# CURSO DE ESTATÍSTICA - PARTE 4
***

# 1 CONHECENDO OS DADOS
***

## 1.1 Dataset do projeto
***

### Pesquisa Nacional por Amostra de Domicílios - 2015

A Pesquisa Nacional por Amostra de Domicílios - PNAD investiga anualmente, de forma permanente, características gerais da população, de educação, trabalho, rendimento e habitação e outras, com periodicidade variável, de acordo com as necessidades de informação para o país, como as características sobre migração, fecundidade, nupcialidade, saúde, segurança alimentar, entre outros temas. O levantamento dessas estatísticas constitui, ao longo dos 49 anos de realização da pesquisa, um importante instrumento para formulação, validação e avaliação de políticas orientadas para o desenvolvimento socioeconômico e a melhoria das condições de vida no Brasil.

### Fonte dos Dados

https://ww2.ibge.gov.br/home/estatistica/populacao/trabalhoerendimento/pnad2015/microdados.shtm

### Variáveis utilizadas

> ### Renda
> ***

Rendimento mensal do trabalho principal para pessoas de 10 anos ou mais de idade.

> ### Idade
> ***

Idade do morador na data de referência em anos.

> ### Altura (elaboração própria)
> ***

Altura do morador em metros.

> ### UF
> ***

|Código|Descrição|
|---|---|
|11|Rondônia|
|12|Acre|
|13|Amazonas|
|14|Roraima|
|15|Pará|
|16|Amapá|
|17|Tocantins|
|21|Maranhão|
|22|Piauí|
|23|Ceará|
|24|Rio Grande do Norte|
|25|Paraíba|
|26|Pernambuco|
|27|Alagoas|
|28|Sergipe|
|29|Bahia|
|31|Minas Gerais|
|32|Espírito Santo|
|33|Rio de Janeiro|
|35|São Paulo|
|41|Paraná|
|42|Santa Catarina|
|43|Rio Grande do Sul|
|50|Mato Grosso do Sul|
|51|Mato Grosso|
|52|Goiás|
|53|Distrito Federal|

> ### Sexo
> ***

|Código|Descrição|
|---|---|
|0|Masculino|
|1|Feminino|

> ### Anos de Estudo
> ***

|Código|Descrição|
|---|---|
|1|Sem instrução e menos de 1 ano|
|2|1 ano|
|3|2 anos|
|4|3 anos|
|5|4 anos|
|6|5 anos|
|7|6 anos|
|8|7 anos|
|9|8 anos|
|10|9 anos|
|11|10 anos|
|12|11 anos|
|13|12 anos|
|14|13 anos|
|15|14 anos|
|16|15 anos ou mais|
|17|Não determinados|
||Não aplicável|

> ### Cor
> ***

|Código|Descrição|
|---|---|
|0|Indígena|
|2|Branca|
|4|Preta|
|6|Amarela|
|8|Parda|
|9|Sem declaração|

#### Observação
***
> Os seguintes tratamentos foram realizados nos dados originais:
> 1. Foram eliminados os registros onde a Renda era inválida (999 999 999 999);
> 2. Foram eliminados os registros onde a Renda era missing;
> 3. Foram considerados somente os registros das Pessoas de Referência de cada domicílio (responsável pelo domicílio).

### Solução do problema com dependência do statsmodels

```python
# Retire o comentário da linha de código abaixo caso precise fazer a correção

# !pip install scipy==1.2 --upgrade
```

### Importando bibliotecas

https://pandas.pydata.org/

https://www.numpy.org/

https://seaborn.pydata.org/

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

### Lendo o dataset do projeto

```python
dados = pd.read_csv('https://raw.githubusercontent.com/Daniel-ASG/Aulas_de_cursos/main/dados.csv')
```

```python
dados.head()
```

      UF
      Sexo
      Idade
      Cor
      Anos de Estudo
      Renda
      Altura

      0
      11
      0
      23
      8
      12
      800
      1.603808

      1
      11
      1
      23
      2
      12
      1150
      1.739790

      2
      11
      1
      35
      8
      15
      880
      1.760444

      3
      11
      0
      46
      2
      6
      3500
      1.783158

      4
      11
      1
      47
      8
      9
      150
      1.690631

---

# 2 RODANDO UMA REGRESSÃO LINEAR
***

## Dataset de exemplo
> ### $Y$ = Gasto das famílias
> ### $X$ = Renda das Famílias

```python
dataset = {
    'Y': [3011, 1305, 1879, 2654, 2849, 1068, 2892, 2543, 3074, 849, 2184, 2943, 1357, 2755, 2163, 3099, 1600, 353, 1778, 740, 2129, 3302, 2412, 2683, 2515, 2395, 2292, 1000, 600, 1864, 3027, 1978, 2791, 1982, 900, 1964, 1247, 3067, 700, 1500, 3110, 2644, 1378, 2601, 501, 1292, 2125, 1431, 2260, 1770],
    'X': [9714, 3728, 6062, 8845, 8378, 3338, 8507, 7947, 9915, 1632, 6825, 8918, 4100, 9184, 6180, 9997, 4500, 1069, 5925, 2466, 6083, 9712, 7780, 8383, 7185, 7483, 7640, 2100, 2000, 6012, 8902, 5345, 8210, 5662, 2700, 6546, 2900, 9894, 1500, 5000, 8885, 8813, 3446, 7881, 1164, 3401, 6641, 3329, 6648, 4800]
}
```

```python
dataset = pd.DataFrame(dataset)
dataset.head()
```

      Y
      X

      0
      3011
      9714

      1
      1305
      3728

      2
      1879
      6062

      3
      2654
      8845

      4
      2849
      8378

```python
dataset.shape
```

    (50, 2)

### Estatísticas descritivas

```python
dataset.describe()
```

      Y
      X

      count
      50.000000
      50.000000

      mean
      2011.120000
      6065.500000

      std
      817.214648
      2707.187812

      min
      353.000000
      1069.000000

      25%
      1362.250000
      3516.500000

      50%
      2127.000000
      6363.000000

      75%
      2675.750000
      8381.750000

      max
      3302.000000
      9997.000000

### Análise gráfica

https://seaborn.pydata.org/generated/seaborn.boxplot.html

```python
ax = sns.boxplot(data=dataset, orient='h', width=0.5)
ax.figure.set_size_inches(12,6)
ax.set_title('Boxplot', fontsize=20)
ax.set_xlabel('Reais (R$)', fontsize=16)
ax;
```

https://seaborn.pydata.org/generated/seaborn.lmplot.html

```python
ax = sns.lmplot(data=dataset, x='X', y='Y')
ax.fig.set_size_inches(12,6)
ax.fig.suptitle('Reta de regressão - Gasto X Renda', fontsize=16, y=1.02)
ax.set_xlabels('Renda das famílias', fontsize=14)
ax.set_ylabels('Gasto das famílias', fontsize=14)
ax;
```

### Análise da correlação

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.corr.html

```python
dataset.corr()
```

      Y
      X

      Y
      1.000000
      0.984835

      X
      0.984835
      1.000000

### Modelo de regressão linear simples

https://www.statsmodels.org/stable/regression.html

```python
import statsmodels.api as sm
```

    /usr/local/lib/python3.7/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
      import pandas.util.testing as tm

```python
Y = dataset.Y
X = sm.add_constant(dataset.X)
```

```python
Y.head()
```

    0    3011
    1    1305
    2    1879
    3    2654
    4    2849
    Name: Y, dtype: int64

```python
X.head()
```

      const
      X

      0
      1.0
      9714

      1
      1.0
      3728

      2
      1.0
      6062

      3
      1.0
      8845

      4
      1.0
      8378

### Estimando o modelo

```python
resultado_regressao = sm.OLS(Y, X).fit()
```

```python
print(resultado_regressao.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                      Y   R-squared:                       0.970
    Model:                            OLS   Adj. R-squared:                  0.969
    Method:                 Least Squares   F-statistic:                     1547.
    Date:                Thu, 01 Apr 2021   Prob (F-statistic):           3.56e-38
    Time:                        17:58:31   Log-Likelihood:                -318.16
    No. Observations:                  50   AIC:                             640.3
    Df Residuals:                      48   BIC:                             644.1
    Df Model:                           1
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    const        207.9033     50.127      4.148      0.000     107.117     308.690
    X              0.2973      0.008     39.328      0.000       0.282       0.312
    ==============================================================================
    Omnibus:                       22.833   Durbin-Watson:                   2.408
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):                3.915
    Skew:                           0.073   Prob(JB):                        0.141
    Kurtosis:                       1.637   Cond. No.                     1.64e+04
    ==============================================================================

    Warnings:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
    [2] The condition number is large, 1.64e+04. This might indicate that there are
    strong multicollinearity or other numerical problems.

### Obtendo as previsões dentro da amostra

```python
dataset['Y_previsto'] = resultado_regressao.predict()
dataset
```

      Y
      X
      Y_previsto

      0
      3011
      9714
      3095.785086

      1
      1305
      3728
      1316.203010

      2
      1879
      6062
      2010.079483

      3
      2654
      8845
      2837.439476

      4
      2849
      8378
      2698.604723

      5
      1068
      3338
      1200.259640

      6
      2892
      8507
      2736.955222

      7
      2543
      7947
      2570.472435

      8
      3074
      9915
      3155.540515

      9
      849
      1632
      693.081721

      10
      2184
      6825
      2236.912280

      11
      2943
      8918
      2859.141696

      12
      1357
      4100
      1426.795147

      13
      2755
      9184
      2938.221020

      14
      2163
      6180
      2045.159784

      15
      3099
      9997
      3179.918352

      16
      1600
      4500
      1545.711423

      17
      353
      1069
      525.707062

      18
      1778
      5925
      1969.350658

      19
      740
      2466
      941.022158

      20
      2129
      6083
      2016.322587

      21
      3302
      9712
      3095.190505

      22
      2412
      7780
      2520.824890

      23
      2683
      8383
      2700.091176

      24
      2515
      7185
      2343.936929

      25
      2395
      7483
      2432.529554

      26
      2292
      7640
      2479.204193

      27
      1000
      2100
      832.213765

      28
      600
      2000
      802.484696

      29
      1864
      6012
      1995.214948

      30
      3027
      8902
      2854.385045

      31
      1978
      5345
      1796.922057

      32
      2791
      8210
      2648.659887

      33
      1982
      5662
      1891.163206

      34
      900
      2700
      1010.588179

      35
      1964
      6546
      2153.968177

      36
      1247
      2900
      1070.046318

      37
      3067
      9894
      3149.297410

      38
      700
      1500
      653.839350

      39
      1500
      5000
      1694.356769

      40
      3110
      8885
      2849.331103

      41
      2644
      8813
      2827.926174

      42
      1378
      3446
      1232.367035

      43
      2601
      7881
      2550.851249

      44
      501
      1164
      553.949678

      45
      1292
      3401
      1218.988954

      46
      2125
      6641
      2182.210793

      47
      1431
      3329
      1197.584024

      48
      2260
      6648
      2184.291828

      49
      1770
      4800
      1634.898631

---

# 3 CORRELAÇÃO
***

## 3.1 Covariância
***

A covariância, ou variância conjunta, é a medida do grau de interdependência (ou inter-relação) numérica entre duas variáveis. É definida da seguinte maneira:

### Covariância populacional

# $$\sigma_{xy} = \frac 1n\sum_{i=1}^{n}(X_i-\mu_x)(Y_i-\mu_y)$$

### Covariância amostral

# $$S_{xy} = \frac 1{n-1}\sum_{i=1}^{n}(X_i-\bar{X})(Y_i-\bar{Y})$$

### Gerando uma amostra aleatória para facilitar o entendimento

```python
amostra = dados.query('Renda < 5000').sample(n=20, random_state=101)
```

### Obtendo a matriz de covariância

```python
amostra[['Idade', 'Renda', 'Anos de Estudo', 'Altura']].cov()
```

      Idade
      Renda
      Anos de Estudo
      Altura

      Idade
      134.410526
      -4947.968421
      -16.410526
      0.016506

      Renda
      -4947.968421
      764668.326316
      2209.021053
      -7.138236

      Anos de Estudo
      -16.410526
      2209.021053
      16.936842
      0.111281

      Altura
      0.016506
      -7.138236
      0.111281
      0.003354

### Identificando as variâncias na diagonal principal da matriz

```python
amostra.Idade.var()
```

    134.4105263157895

## 3.2 Interpretação da Covariância
***

Valor de $S_{xy}$ positivo indica uma associação linear positiva entre x e y, ou seja, à medida que o valor de x aumenta, o valor de y também aumenta. Neste caso, podemos ver na figura abaixo que os pontos que têm a maior influência sobre $S_{xy}$ devem estar nos quadrantes I e III.

Se o valor de $S_{xy}$ for negativo temos um indicativo de associação linear negativa entre x e y, ou seja, à medida que x aumenta, o valor de y diminui. Neste caso, podemos ver na figura abaixo que os pontos que têm a maior influência sobre $S_{xy}$ devem estar nos quadrantes II e IV.

Finalmente, se os pontos estiverem uniformemente distribuídos pelos quadrantes, o valor de $S_{xy}$ se aproximará de zero, indicando que não existe nenhuma associação linear entre x e y.

### Verificando a existência de uma associação linear negativa

```python
x = amostra.Renda
y = amostra.Idade

ax = sns.scatterplot(x,y)
ax.figure.set_size_inches(10,6)
ax.hlines(y.mean(), x.min(), x.max(), colors='black', linestyles='dashed')
ax.vlines(x.mean(), y.min(), y.max(), colors='black', linestyles='dashed')
```

### Verificando a existência de uma associação linear positiva

```python
x = amostra.Renda
y = amostra['Anos de Estudo']

ax = sns.scatterplot(x,y)
ax.figure.set_size_inches(10,6)
ax.hlines(y.mean(), x.min(), x.max(), colors='black', linestyles='dashed')
ax.vlines(x.mean(), y.min(), y.max(), colors='black', linestyles='dashed')
```

### Verificando a inexistência de uma associação linear entre as variáveis

```python
x = amostra.Idade
y = amostra.Altura

ax = sns.scatterplot(x,y)
ax.figure.set_size_inches(10,6)
ax.hlines(y.mean(), x.min(), x.max(), colors='black', linestyles='dashed')
ax.vlines(x.mean(), y.min(), y.max(), colors='black', linestyles='dashed')
```

### Observação Importante:
> Pelo que foi apresentado pode parecer que valores elevados, tanto positivos quanto negativos, para a covariância indicam relações lineares fortes entre as variáveis envolvidas. No entanto, um problema quando se usa a covariância como uma medida da intensidade da relação linear é que o valor da covariância depende das unidades de medida para x e y.
>
> Uma medida da relação entre duas variáveis que não é afetada pelas unidades de medida para x e y é o coeficiente de correlação que veremos no próximo tópico.

## 3.3 Coeficiente de correlação de Pearson
***

É obtido dividindo-se a covariância da população ou amostra pelo produto do desvio padrão populacional ou amostral de x pelo desvio padrão populacional ou amostral de y.

O coeficiente de correlação varia de -1 a +1. Valores que se aproximam de -1 ou +1 indicam uma relação linear forte. Quanto mais próxima a correlação estiver de zero, mais fraca será a relação.

Um ponto importante é que o coeficiente de correlação é uma medida de associação linear e não necessariamente de causação. Uma correlação alta entre duas variáveis não significa, necessariamente, que variações em uma delas provocará alterações na outra.

### Coeficiente de correlação de Pearson - dados populacionais

# $$\rho_{xy} = \frac{\sigma_{xy}}{\sigma_{x}\sigma_{y}}$$

### Coeficiente de correlação de Pearson - dados amostrais

# $$r_{xy} = \frac{s_{xy}}{s_{x}s_{y}}$$

Onde

$\sigma_{xy}$ = covariância populacional entre x e y

$s_{xy}$ = covariância amostral entre x e y

$\sigma_{x}$ e $\sigma_{y}$ = desvios padrão populacionais de x e y, respectivamente

$s_{x}$ e $s_{y}$ = desvios padrão amostrais de x e y, respectivamente

### Obtendo $s_{xy}$

```python
s_xy = dados[['Altura', 'Renda']].cov()
s_xy
```

      Altura
      Renda

      Altura
      0.006355
      -1.078964e+00

      Renda
      -1.078964
      1.104491e+07

```python
s_xy = s_xy.Altura.loc['Renda']
s_xy
```

    -1.0789639314252726

### Obtendo $s_x$ e $s_y$

```python
s_x = dados.Altura.std()
s_y = dados.Renda.std()
```

### Obtendo o coeficiente de correlação $r_{xy}$

```python
r_xy = s_xy / (s_x * s_y)
r_xy
```

    -0.0040726081433995155

### Obtendo uma matriz de correlação com o Pandas

```python
dados[['Altura', 'Renda']].corr()
```

      Altura
      Renda

      Altura
      1.000000
      -0.004073

      Renda
      -0.004073
      1.000000

```python
dataset[['Y', 'X']].corr()
```

      Y
      X

      Y
      1.000000
      0.984835

      X
      0.984835
      1.000000

```python
x = amostra.Renda
y = amostra.Altura

ax = sns.scatterplot(x,y)
ax.figure.set_size_inches(10,6)
ax.hlines(y.mean(), x.min(), x.max(), colors='black', linestyles='dashed')
ax.vlines(x.mean(), y.min(), y.max(), colors='black', linestyles='dashed')
```

```python
x = dataset.Y
y = dataset.X

ax = sns.scatterplot(x,y)
ax.figure.set_size_inches(10,6)
ax.hlines(y.mean(), x.min(), x.max(), colors='black', linestyles='dashed')
ax.vlines(x.mean(), y.min(), y.max(), colors='black', linestyles='dashed')
```

---

# 4 REGRESSÃO LINEAR
***

A análise de regressão diz respeito ao estudo da dependência de uma variável (a variável dependente) em relação a uma ou mais variáveis, as variáveis explicativas ou independentes, visando estimar e/ou prever o valor médio da primeira em termos dos valores conhecidos ou fixados das segundas.

#### Terminologia

> A variável que é prevista é conhecida como variável dependente (*Y*).

> A variável utilizada para prever o valor da variável dependente é conhecida como variável independente (*X*).

Em nosso treinamento vamos abordar o tipo mais simples de análise de regressão que considera uma variável dependente e apenas uma variável independente onde a relação entre as variáveis se aproxima de uma linha reta.

## 4.1 Regressão linear simples
***

### Função consumo

Um economista famoso do passado concluiu em um de seus estudos que os indivíduos tendem a aumentar o seu consumo conforme sua renda aumenta. Logicamente esta teoria ganhou um pouco mais de complexidade, mas vamos utilizar sua forma mais simples para entender o procedimento de análise de regressão com a seguninte formulação:

# $$Y_i = \beta_1 + \beta_2X_i$$

Onde

- $Y_i$ é o gasto das famílias ou consumo das famílias

- $X_i$ é a renda disponível

- $\beta_1$ é conhecido como intercepto (no modelo teórico acima é conhecido como consumo autônomo, ou seja, o consumo quando o rendimento é zero)

- $\beta_2$ é o coefiente angular (no modelo teórico é a propensão marginal a consumir)

Em uma análise de regressão linear simples, o interesse está em estimar a função de regressão populacional como a apresentada acima, ou seja, estimar os valores dos parâmetros $\beta_1$ e $\beta_2$ com base nas observações de $Y$ e $X$.

### Carregando o dataset

```python
dataset = {
    'Y': [3011, 1305, 1879, 2654, 2849, 1068, 2892, 2543, 3074, 849, 2184, 2943, 1357, 2755, 2163, 3099, 1600, 353, 1778, 740, 2129, 3302, 2412, 2683, 2515, 2395, 2292, 1000, 600, 1864, 3027, 1978, 2791, 1982, 900, 1964, 1247, 3067, 700, 1500, 3110, 2644, 1378, 2601, 501, 1292, 2125, 1431, 2260, 1770],
    'X': [9714, 3728, 6062, 8845, 8378, 3338, 8507, 7947, 9915, 1632, 6825, 8918, 4100, 9184, 6180, 9997, 4500, 1069, 5925, 2466, 6083, 9712, 7780, 8383, 7185, 7483, 7640, 2100, 2000, 6012, 8902, 5345, 8210, 5662, 2700, 6546, 2900, 9894, 1500, 5000, 8885, 8813, 3446, 7881, 1164, 3401, 6641, 3329, 6648, 4800]
}
```

```python
dataset = pd.DataFrame(dataset)
dataset.head()
```

      Y
      X

      0
      3011
      9714

      1
      1305
      3728

      2
      1879
      6062

      3
      2654
      8845

      4
      2849
      8378

### Identificando a relação entre as variáveis

https://seaborn.pydata.org/generated/seaborn.lmplot.html

Plota a reta de regressão entre duas variáveis juntamente com a dispersão entre elas.

```python
ax = sns.lmplot(data=dataset, x='X', y='Y')
ax.fig.set_size_inches(12,6)
ax.fig.suptitle('Reta de regressão - Gasto X Renda', fontsize=16, y=1.02)
ax.set_xlabels('Renda das famílias', fontsize=14)
ax.set_ylabels('Gasto das famílias', fontsize=14)
ax;
```

### Matriz de correlação

```python
dataset.corr()
```

      Y
      X

      Y
      1.000000
      0.984835

      X
      0.984835
      1.000000

### Função de regressão populacional

A equação formulada acima é conhecida como **função de regressão populacional (FRP)** e em alguns livros é também representada da seguinte forma:

# $$E(Y|X_i) = \beta_1 + \beta_2X_i$$

Ela afirma que o valor esperado da distribuição de $Y$, dado $X_i$, tem uma relação funcional com $X_i$, isto é, a resposta média de $Y$ varia com $X$. O coeficientes $\beta_1$ e $\beta_2$ são conhecidos como coeficientes de regressão e também são conhecidos como intercepto e coeficiente angular, respectivamente.

O gráfico de dispersão acima deixa claro que com o aumento da renda familiar, em média, as despesas de consumo das famílias aumentam, mas não no mesmo nível. O que podemos dizer é que para um nível de renda $X_i$ as despesas médias de consumo de uma
família agrupam-se em torno do consumo médio de todas as famílias deste nível $X_i$, isto é, em torno de sua esperança condicional $E(Y|X_i)$. Portanto, podemos expressar o desvio individual de $Y_i$ em torno de seu valor esperado da seguinte forma:

# $$u_i = Y_i - E(Y|X_i)$$

ou

# $$Y_i = E(Y|X_i) + u_i$$

onde o desvio $u_i$ é uma variável aleatória não-observável que assume valores positivos ou negativos. Esta variável é também cohecida como termo de erro estocástico.

Observe que a podemos representar a FRP em sua forma estocástica da seguinte maneira:

# $$Y_i = \beta_1 + \beta_2X_i + u_i$$

## 4.2 O método de mínimos quadrados
***

Considere a seguinte **função de regressão da população (FRP)** para duas variáveis:

# $$Y_i = \beta_1 + \beta_2X_i + u_i$$

Como a **FRP** não pode ser obtida de forma direta, precisamos estimá-la através da **função de regressão amostral (FRA)**.

# $$Y_i = \hat{\beta}_1 + \hat{\beta}_2X_i + \hat{u}_i$$
# $$Y_i = \hat{Y}_i + \hat{u}_i$$

onde $\hat{Y}_i$ é o valor estimado de $Y_i$.

A partir da equação acima podemos expressar o erro da seguinte maneira:

# $$\hat{u}_i = Y_i - \hat{Y}_i$$
# $$\hat{u}_i = Y_i - \hat{\beta}_1 - \hat{\beta}_2X_i$$

A ideia é determinar **FRA** de forma que fique o mais próximo possível do valor observado de $Y$. Intuitivamente uma forma interessante de fazer isso seria determinar **FRA** de maneira que a soma dos resíduos seja a menor possível.

# $$\sum\hat{u}_i = \sum{(Y_i - \hat{Y}_i)}$$

Avaliando a figura abaixo se pode verificar que talvez o critério de minimizar a soma dos resíduos não seja a melhor abordagem para solucionar o problema.

O critério de minimização da soma dos resíduos assume que todos os resíduos tem o mesmo peso no somatório, o que nem sempre se comprova. Em nosso exemplo os resíduos $\hat{u}_1$ e $\hat{u}_4$ encontram-se bem mais afastados da **FRA** que $\hat{u}_2$ e $\hat{u}_3$. Isso acarreta na possibilidade de que a soma dos $\hat{u}_i$'s seja bastante pequena e até mesmo nula, mesmo que os resíduos estejam muito dispersos em relação a reta de regressão.

Para evitar tal problema adotou-se o critério de minimização da soma dos quadrados dos resíduos que é conhecido como **Método de Mínimos Quadrados**.

# $$\sum\hat{u}_i^2 = \sum{(Y_i - \hat{Y}_i)^2}$$
# $$\sum\hat{u}_i^2 = \sum{(Y_i - \hat{\beta}_1 - \hat{\beta}_2X_i)^2}$$

Após um procedimento de diferenciação, algumas simplificações e manipulações algébricas obtemos os **estimadores de mínimos quadrados**.

# $$
\begin{equation}
\boxed{\hat{\beta}_2 = \frac{n\sum{X_iY_i} - \sum{X_i}\sum{Y_i}}{n\sum{X_i^2} - (\sum{X_i})^2}}
\end{equation}
$$

# $$
\begin{equation}
\boxed{
\begin{eqnarray}
\hat{\beta}_1 &=& \frac{\sum{X_i^2}\sum{Y_i} - \sum{X_i}\sum{X_iY_i}}{n\sum{X_i^2} - (\sum{X_i})^2}\\
&=& \bar{Y} - \hat{\beta}_2\bar{X}
\end{eqnarray}
}
\end{equation}
$$

## 4.3 Estimadores de mínimos quadrados ordinários
***

### Obter $n$

```python
n = len(dataset)
n
```

    50

### Obter $\sum{Y}$

```python
SOMA_Y = dataset.Y.sum()
SOMA_Y
```

    100556

### Obter $\sum{X}$

```python
SOMA_X = dataset.X.sum()
SOMA_X
```

    303275

### Obter $\sum{X^2}$

```python
dataset['X2'] = dataset.X**2
dataset.head()
```

      Y
      X
      X2

      0
      3011
      9714
      94361796

      1
      1305
      3728
      13897984

      2
      1879
      6062
      36747844

      3
      2654
      8845
      78234025

      4
      2849
      8378
      70190884

```python
SOMA_X2 = dataset.X2.sum()
SOMA_X2
```

    2198628939

```python
SOMA_X2 = dataset.X.apply(lambda x: x**2).sum()
SOMA_X2
```

    2198628939

### Obter $\sum{Y^2}$

```python
SOMA_Y2 = dataset.Y.apply(lambda y: y**2).sum()
SOMA_Y2
```

    234954332

### Obter $\sum{XY}$

```python
dataset['XY'] = dataset.X * dataset.Y
dataset.head()
```

      Y
      X
      X2
      XY

      0
      3011
      9714
      94361796
      29248854

      1
      1305
      3728
      13897984
      4865040

      2
      1879
      6062
      36747844
      11390498

      3
      2654
      8845
      78234025
      23474630

      4
      2849
      8378
      70190884
      23868922

```python
SOMA_XY = dataset.XY.sum()
SOMA_XY
```

    716683794

```python
SOMA_XY = dataset.apply(lambda data: data.X * data.Y, axis=1).sum()
SOMA_XY
```

    716683794

```python
dataset.drop(['X2', 'XY'], axis=1, inplace=True)
dataset.head()
```

      Y
      X

      0
      3011
      9714

      1
      1305
      3728

      2
      1879
      6062

      3
      2654
      8845

      4
      2849
      8378

### Obter $\hat{\beta}_2$

# $$\hat{\beta}_2 = \frac{n\sum{X_iY_i} - \sum{X_i}\sum{Y_i}}{n\sum{X_i^2} - (\sum{X_i})^2}$$

```python
numerador = n * SOMA_XY - SOMA_X * SOMA_Y
denominador = n * SOMA_X2 - (SOMA_X)**2
beta_2 = numerador / denominador
beta_2
```

    0.29729069099372424

### Obter $\hat{\beta}_1$

# $$
\begin{eqnarray}
\hat{\beta}_1 &=& \frac{\sum{X_i^2}\sum{Y_i} - \sum{X_i}\sum{X_iY_i}}{n\sum{X_i^2} - (\sum{X_i})^2}\\
&=& \bar{Y} - \hat{\beta}_2\bar{X}
\end{eqnarray}
$$

```python
beta_1 = dataset.Y.mean() - beta_2 * dataset.X.mean()
beta_1
```

    207.90331377756547

### Obtendo a estimativa dos parâmetros com o StatsModels

### Importando a biblioteca
https://www.statsmodels.org/stable/index.html

```python
import statsmodels.api as sm
```

```python
Y = dataset.Y
X = sm.add_constant(dataset.X)
```

```python
Y.head()
```

    0    3011
    1    1305
    2    1879
    3    2654
    4    2849
    Name: Y, dtype: int64

```python
X.head()
```

      const
      X

      0
      1.0
      9714

      1
      1.0
      3728

      2
      1.0
      6062

      3
      1.0
      8845

      4
      1.0
      8378

### Estimando o modelo

```python
resultado_regressao = sm.OLS(Y, X, missing='drop').fit()
```

### Visualizando os parâmetros estimados

```python
beta_1
```

    207.90331377756547

```python
beta_2
```

    0.29729069099372424

```python
resultado_regressao.params
```

    const    207.903314
    X          0.297291
    dtype: float64

```python
beta_1 = resultado_regressao.params[0]
beta_1
```

    207.90331377756553

```python
beta_2 = resultado_regressao.params[1]
beta_2
```

    0.29729069099372435

### Intervalo de confiança para os parâmetros estimados

```python
resultado_regressao.conf_int(alpha=0.05)
```

      0
      1

      const
      107.116864
      308.689763

      X
      0.282092
      0.312490

## 4.4 Obtendo previsões
***

# $$\hat{Y}_i = 207,9033 + 0,2973X_i$$

### Previsões dentro da amostra

```python
dataset['Y_previsto'] = beta_1 + beta_2 * dataset.X
dataset.head(10)
```

      Y
      X
      Y_previsto

      0
      3011
      9714
      3095.785086

      1
      1305
      3728
      1316.203010

      2
      1879
      6062
      2010.079483

      3
      2654
      8845
      2837.439476

      4
      2849
      8378
      2698.604723

      5
      1068
      3338
      1200.259640

      6
      2892
      8507
      2736.955222

      7
      2543
      7947
      2570.472435

      8
      3074
      9915
      3155.540515

      9
      849
      1632
      693.081721

### Utilizando o statsmodels

```python
dataset['Y_previsto_statsmodels'] = resultado_regressao.predict()
dataset.head()
```

      Y
      X
      Y_previsto
      Y_previsto_statsmodels

      0
      3011
      9714
      3095.785086
      3095.785086

      1
      1305
      3728
      1316.203010
      1316.203010

      2
      1879
      6062
      2010.079483
      2010.079483

      3
      2654
      8845
      2837.439476
      2837.439476

      4
      2849
      8378
      2698.604723
      2698.604723

```python
dataset.drop(['Y_previsto_statsmodels'], axis=1, inplace=True)
dataset.head()
```

      Y
      X
      Y_previsto

      0
      3011
      9714
      3095.785086

      1
      1305
      3728
      1316.203010

      2
      1879
      6062
      2010.079483

      3
      2654
      8845
      2837.439476

      4
      2849
      8378
      2698.604723

```python

```

### Estimando o 'Gasto das Famílias' fora da amostra

```python
def prever(x):
    return (beta_1 + beta_2 * x).round(2)
```

```python
prever(7510)
```

    2440.56

### Estimando o 'Gasto das Famílias' fora da amostra via StatsModels

```python
resultado_regressao.predict([1, 7510])[0].round(2)
```

    2440.56

## 4.5 Resíduos
***

Como vimos anteriormente, o resíduo da i-ésima observação é a diferença entre o valor observado de nossa variável dependente ($Y_i$) e o valor estimado da variável dependente ($\hat{Y}_i$).

# $$\hat{u}_i = Y_i - \hat{Y}_i$$

Em outras palavras, $\hat{u}_i$ é o erro obtido ao se utilizar a equação de regressão estimada para prever o valor da variável dependente.

```python
dataset['u'] = dataset.Y - dataset.Y_previsto
dataset.head()
```

      Y
      X
      Y_previsto
      u

      0
      3011
      9714
      3095.785086
      -84.785086

      1
      1305
      3728
      1316.203010
      -11.203010

      2
      1879
      6062
      2010.079483
      -131.079483

      3
      2654
      8845
      2837.439476
      -183.439476

      4
      2849
      8378
      2698.604723
      150.395277

```python
dataset['Residuos'] = resultado_regressao.resid
dataset.head()
```

      Y
      X
      Y_previsto
      u
      Residuos

      0
      3011
      9714
      3095.785086
      -84.785086
      -84.785086

      1
      1305
      3728
      1316.203010
      -11.203010
      -11.203010

      2
      1879
      6062
      2010.079483
      -131.079483
      -131.079483

      3
      2654
      8845
      2837.439476
      -183.439476
      -183.439476

      4
      2849
      8378
      2698.604723
      150.395277
      150.395277

```python
dataset.drop(['u'], axis=1, inplace=True)
dataset.head()
```

      Y
      X
      Y_previsto
      Residuos

      0
      3011
      9714
      3095.785086
      -84.785086

      1
      1305
      3728
      1316.203010
      -11.203010

      2
      1879
      6062
      2010.079483
      -131.079483

      3
      2654
      8845
      2837.439476
      -183.439476

      4
      2849
      8378
      2698.604723
      150.395277

```python
dataset.Residuos.mean()
```

    -5.570655048359186e-13

## 4.6 Suposições sobre o termo de erro $u$
***

### 1. O termo de erro $u$ é uma variável aleatória com média igual a zero: $E(u) = 0$
### 2. A variância de $u$ é a mesma para todos os valores de $X$
### 3. O valores de $u$ são independentes
### 4. O termo de erro $u$ é uma variável aleatória normalmente distribuída.

### Plotando os resíduos do modelo

https://seaborn.pydata.org/generated/seaborn.scatterplot.html

```python
ax = sns.scatterplot(x=dataset.X, y=dataset.Residuos)
ax.figure.set_size_inches(12,6)
ax.set_title('Resíduos vs Variável Independente', fontsize=18)
ax.set_xlabel('X', fontsize=14)
ax.set_ylabel('Resíduos', fontsize=14)
ax;
```

```python
ax = sns.scatterplot(x=dataset.Y_previsto, y=dataset.Residuos)
ax.figure.set_size_inches(12,6)
ax.set_title('Resíduos vs Y_previsto', fontsize=18)
ax.set_xlabel('Y_previsto', fontsize=14)
ax.set_ylabel('Resíduos', fontsize=14)
ax;
```

### Hipótese de variância constante

Fonte: Econometria Básica - 5ª edição - Gujarati e Porter

```python
ax = sns.scatterplot(x=dataset.Y_previsto, y=dataset.Residuos**2)
ax.figure.set_size_inches(12,6)
ax.set_title('Resíduos² vs Y_previsto', fontsize=18)
ax.set_xlabel('Y_previsto', fontsize=14)
ax.set_ylabel('Resíduos²', fontsize=14)
ax;
```

## 4.7 O coeficiente de determinação $R^2$
***

O **coeficiente de determinação R²** é uma medida resumida que diz quanto a linha de regressão amostral se ajusta aos dados. Pode ser obtido a partir da seguinte fórmula:

# $$R^2 = \frac{\big[\sum{(Y_i - \bar{Y})(\hat{Y}_i - \bar{Y})}\big]^2}{\sum{(Y_i - \bar{Y}})^2 \sum{(\hat{Y}_i - \bar{Y}})^2}$$

Sabemos que o i-ésimo resíduo representa o erro de usarmos $\hat{Y}_i$ para estimar $Y_i$. A soma dos quadrados desses resíduos é o valor que é minimizado pelo método dos mínimos quadrados. Esse valor pode ser representado da seguinte forma:

# $$SQE = \sum{(Y_i - \hat{Y}_i)^2}$$

O valor da SQE é uma medida do erro de se usar a equação de regressão estimada para estimar os valores da variável dependente na amostra.

Outro componente que podemos medir é a soma dos quadrados total (SQT) que representa a medida do erro envolvido no uso da média ($\bar{Y}$) para fazer as estimativas. A SQT pode ser representada da forma abaixo:

# $$SQT = \sum{(Y_i - \bar{Y})^2}$$

Para quantificar o quanto os valores estimados ($\hat{Y}_i$) se afastam da média ($\bar{Y}$) podemos obter mais uma soma de quadrados. Essa soma é chamada de soma dos quadrados da regressão (SQR) e é representada pela seguinte fórmula:

# $$SQR = \sum{(\hat{Y}_i - \bar{Y})^2}$$

### Soma do quadrados do erros (SQE)

```python
dataset.head()
```

      Y
      X
      Y_previsto
      Residuos

      0
      3011
      9714
      3095.785086
      -84.785086

      1
      1305
      3728
      1316.203010
      -11.203010

      2
      1879
      6062
      2010.079483
      -131.079483

      3
      2654
      8845
      2837.439476
      -183.439476

      4
      2849
      8378
      2698.604723
      150.395277

```python
SQE = dataset.Residuos.apply(lambda u: u**2).sum()
SQE
```

    984986.0375191914

```python
resultado_regressao.ssr
```

    984986.0375191915

### Soma do quadrados total (SQT)

```python
SQT = dataset.Y.apply(lambda y: (y - dataset.Y.mean())**2).sum()
SQT
```

    32724149.279999997

### Soma do quadrados da regressão (SQR)

```python
SQR = dataset.Y_previsto.apply(lambda y: (y - dataset.Y.mean())**2).sum()
SQR
```

    31739163.24248083

```python
resultado_regressao.ess
```

    31739163.242480807

### Relação entre as somas de quadrados

# $$SQT = SQR + SQE$$

Onde,

### $SQE = \sum{(Y_i - \hat{Y}_i)^2}$

### $SQT = \sum{(Y_i - \bar{Y})^2}$

### $SQR = \sum{(\hat{Y}_i - \bar{Y})^2}$

```python

```

### Coeficiente de determinação (R²)

A regressão estimada teria um ajuste perfeito se todos os valores da variável dependente ($Y_i$) estiverem sobre a reta de regressão estimada. Quando isso ocorre temos $Y_i - \hat{Y}_i = 0$ para todas as observações, o que resulta em uma $SQE = 0$. Como $SQT=SQR+SQE$, é possível deduzir que para termos um ajuste perfeito $SQT$ deve ser igual a $SQR$ e a razão entre estas medidas ($\frac{SQR}{SQT}$) deve ser igual a 1.

Quando o ajuste não é tão perfeito nota-se que a $SQE$ apresenta valores maiores o que faz a razão $\frac{SQR}{SQT}$ diminuir, e em uma situação de total imperfeição do ajuste teríamos uma $SQR=0$ e $SQE=SQT$ levando a $\frac{SQR}{SQT}=0$.

A razão $\frac{SQR}{SQT}$, que assume valores entre 0 e 1, é utilizada como medida de eficiência do ajuste da equação de regressão estimada. Essa medida é conhecida como coeficiente de determinação (R²).

# $$R^2 = \frac{SQR}{SQT}$$

## Observação
> Somente com a utilização do R² não é possível tirar conclusões sobre a relação entre $Y$ e $X$ ser ou não ser estatisticamente
significativa. Este tipo de afirmação deve basear-se em considerações que envolvem o tamanho da amostra e as propriedades da distribuição amostral dos estimadores mínimos quadrados.

```python
R2 = SQR / SQT
R2
```

    0.9699003317369304

```python
resultado_regressao.rsquared
```

    0.9699003317369297

## 4.8 Testes aplicados a modelos de regressão
***

Como vimos, em uma regressão linear simples, a média da variável dependente ($Y$) é uma função linear da variável independente ($X$):

# $$Y_i = \beta_1 + \beta_2X_i$$

Se o valor de $\beta_2$ for zero podemos verificar que o valor médio de $Y$ não depende do valor de $X$ e, portanto, concluímos que $X$ e $Y$ não estão linearmente relacionados. De forma alternativa, se o valor de $\beta_2$ não for igual a zero, concluímos que as duas variáveis estão relacionadas.

Para testar se a relação de regressão é significativa, é preciso realizar um **teste de hipóteses** para determinar se o valor de $\beta_2$ é zero. Antes de realizar este tipo de teste precisamos obter uma estimativa para $\sigma^2$ (variância do erro).

### Output do modelo de regressão estimado

```python
print(resultado_regressao.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                      Y   R-squared:                       0.970
    Model:                            OLS   Adj. R-squared:                  0.969
    Method:                 Least Squares   F-statistic:                     1547.
    Date:                Thu, 01 Apr 2021   Prob (F-statistic):           3.56e-38
    Time:                        17:58:50   Log-Likelihood:                -318.16
    No. Observations:                  50   AIC:                             640.3
    Df Residuals:                      48   BIC:                             644.1
    Df Model:                           1
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    const        207.9033     50.127      4.148      0.000     107.117     308.690
    X              0.2973      0.008     39.328      0.000       0.282       0.312
    ==============================================================================
    Omnibus:                       22.833   Durbin-Watson:                   2.408
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):                3.915
    Skew:                           0.073   Prob(JB):                        0.141
    Kurtosis:                       1.637   Cond. No.                     1.64e+04
    ==============================================================================

    Warnings:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
    [2] The condition number is large, 1.64e+04. This might indicate that there are
    strong multicollinearity or other numerical problems.

### Erro quadrático médio - estimativa de $\sigma^2$

Lembre-se que a **soma dos quadrados do erros (SQE)** é uma medida numérica da variabilidade dos dados observados em torno da reta de regressão estimada. Todas as somas de quadrados, estudadas anteriormente, estão associadas a um determinado número de graus de liberdade. No caso da SQE, como devem ser estimados dois parâmetros, temos $n-2$ graus de liberdade.

O **erro quadrático médio**, representado pela equação abaixo, pode ser obtido dividindo-se a SQE por $n-2$ graus de liberdade.

# $$EQM = \frac{SQE}{n-2}$$

```python
SQE
```

    984986.0375191914

```python
n
```

    50

```python
EQM = SQE / (n - 2)
EQM
```

    20520.542448316486

```python
EQM = resultado_regressao.mse_resid
EQM
```

    20520.54244831649

### Teste de hipótese para nulidade do coeficiente angular

Considere o seguinte modelo de regressão linear simples:

# $$Y_i = \beta_1 + \beta_2X_i + u_i$$

Se as variáveis $Y$ e $X$ são linearmente relacionadas, espera-se que $\beta_2$ seja diferente de zero. Para testar esta hipótese formulamos um teste de hipótese com a seguinte especificação de hipóteses nula e alternativa:

## $H_0: \beta_2 = 0$
## $H_1: \beta_2 \neq 0$

Caso $H_0$ seja rejeitada, concluiremos que existe uma relação linear estatisticamente significativa entre as duas variáveis.

Considere agora que $b_1$ e $b_2$ são nossos estimadores de mínimos quadrados. Considere que a distribuição amostral de $b_2$ segue uma normal e também as seguintes propriedades:

# $$E(b_2) = \beta_2$$
# $$\sigma_{b_2} = \frac{\sigma}{\sqrt{\sum{(X_i - \bar{X})^2}}}$$

Como não conhecemos o valor de $\sigma$, utilizamos $s$ como estimativa:

# $$s_{b_2} = \frac{s}{\sqrt{\sum{(X_i - \bar{X})^2}}}$$

Note que o valor esperado de $b_2$ é $\beta_2$, logo, $b_2$ é um estimador não viesado de $\beta_2$. Abaixo temos a estatística de teste (t) que segue uma distribuição t de Student com $n-2$ graus de liberdade.

# $$t = \frac{b_2 - \beta_2}{s_{b_2}}$$

### Calculando $s$

```python
s = np.sqrt(resultado_regressao.mse_resid)
s
```

    143.24993001155877

### Calculando $\sum{(X_i - \bar{X})^2}$

```python
SOMA_DESVIO2 = dataset.X.apply(lambda x: (x - dataset.X.mean())**2).sum()
SOMA_DESVIO2
```

    359114426.5

### Calculando $s_{b_2}$

```python
s_beta_2 = s / np.sqrt(SOMA_DESVIO2)
s_beta_2
```

    0.00755923753376846

### Determinando as áreas de aceitação e rejeição de $H_0$

```python
from scipy.stats import t as t_student
```

### Níveis de confiança e significância

```python
confianca = 0.95
significancia = 1 - confianca
```

```python
graus_de_liberdade = resultado_regressao.df_resid
graus_de_liberdade
```

    48.0

### Obtendo $t_{\alpha/2}$

```python
probabilidade = (0.5 + (confianca/2))
probabilidade
```

    0.975

```python
t_alpha_2 = t_student.ppf(probabilidade, graus_de_liberdade)
t_alpha_2
```

    2.0106347546964454

### Obtendo $t = \frac{b_2 - \beta_2}{s_{b_2}}$

```python
t = (beta_2 - 0) / s_beta_2
t
```

    39.328131926755034

```python
resultado_regressao.tvalues[1]
```

    39.32813192675501

### Etapas e regras de decisão do teste t de significância dos parâmetros

### Critério do valor crítico

> ### Teste Bicaudal
> ### Rejeitar $H_0$ se $t \leq -t_{\alpha / 2}$ ou se $t \geq t_{\alpha / 2}$

```python
t <= -t_alpha_2
```

    False

```python
t >= -t_alpha_2
```

    True

### Critério do $p-valor$

> ### Teste Bicaudal
> ### Rejeitar $H_0$ se o valor $p\leq\alpha$

```python
p_valor = 2 * (t_student.sf(t, graus_de_liberdade))
p_valor
```

    3.555543228585887e-38

```python
p_valor = resultado_regressao.pvalues[1]
p_valor
```

    3.5555432285859883e-38

```python
p_valor <= significancia
```

    True

### Conclusão: Rejeitamos $H_0$ e concluímos que existe uma relação significativa entre as duas variáveis.

```python
print(resultado_regressao.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                      Y   R-squared:                       0.970
    Model:                            OLS   Adj. R-squared:                  0.969
    Method:                 Least Squares   F-statistic:                     1547.
    Date:                Thu, 01 Apr 2021   Prob (F-statistic):           3.56e-38
    Time:                        18:19:25   Log-Likelihood:                -318.16
    No. Observations:                  50   AIC:                             640.3
    Df Residuals:                      48   BIC:                             644.1
    Df Model:                           1
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    const        207.9033     50.127      4.148      0.000     107.117     308.690
    X              0.2973      0.008     39.328      0.000       0.282       0.312
    ==============================================================================
    Omnibus:                       22.833   Durbin-Watson:                   2.408
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):                3.915
    Skew:                           0.073   Prob(JB):                        0.141
    Kurtosis:                       1.637   Cond. No.                     1.64e+04
    ==============================================================================

    Warnings:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
    [2] The condition number is large, 1.64e+04. This might indicate that there are
    strong multicollinearity or other numerical problems.

---

### Teste F

O teste F também é uma ferramenta para testar a significância na regressão. Baseado na distribuição F de probabilidade, o teste F é utilizado para verificar a significância global na regressão, isto é, em uma regressão múltipla, onde existe mais de uma variável independente, o teste F verifica a nulidade de todos os parâmetros do modelo conjuntamente.

Em nosso caso (regressão linear simples) ele fornece a mesma conclusão obtida com o teste t.

**Hipóteses:**

## $H_0: \beta_2 = 0$
## $H_0: \beta_2 \neq 0$

**Estatística de teste:**

# $$F = \frac{\frac{SQR}{k}}{\frac{SQE}{n-k-1}}$$

Onde,

$SQR$ - soma dos quadrados da regressão

$SQE$ - soma dos quadrados dos erros

$k$ - total de variáveis independentes ($X$)

**Regras de rejeição de $H_0$:**

**Critério do valor crítico** $\rightarrow$ Rejeitar se $F \geq F_{\alpha}$

Onde,

$F_{\alpha}$ - baseia-se na distribuição F com $k$ graus de liberdade no numerador e $n-k-1$ no denominador.

**Critério do p-valor** $\rightarrow$ Rejeitar se $p-valor \leq \alpha$

### Calculando a estatística de teste ($F$)

```python
resultado_regressao.mse_model
```

    31739163.242480807

```python
resultado_regressao.mse_resid
```

    20520.54244831649

```python
F = resultado_regressao.mse_model / resultado_regressao.mse_resid
F
```

    1546.7019608482472

```python
resultado_regressao.fvalue
```

    1546.7019608482472

### Obtendo o p-valor

```python
resultado_regressao.f_pvalue
```

    3.5555432285860227e-38

```python
from scipy.stats import f
```

```python
p_valor = f.sf(F, 1, 48)
p_valor
```

    3.5555432285860227e-38

```python
p_valor <= 0.05
```

    True

# 5 EXTRAS
***

## 5.1 Outros testes
***

```python
print(resultado_regressao.summary())
```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                      Y   R-squared:                       0.970
    Model:                            OLS   Adj. R-squared:                  0.969
    Method:                 Least Squares   F-statistic:                     1547.
    Date:                Thu, 01 Apr 2021   Prob (F-statistic):           3.56e-38
    Time:                        18:29:10   Log-Likelihood:                -318.16
    No. Observations:                  50   AIC:                             640.3
    Df Residuals:                      48   BIC:                             644.1
    Df Model:                           1
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    const        207.9033     50.127      4.148      0.000     107.117     308.690
    X              0.2973      0.008     39.328      0.000       0.282       0.312
    ==============================================================================
    Omnibus:                       22.833   Durbin-Watson:                   2.408
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):                3.915
    Skew:                           0.073   Prob(JB):                        0.141
    Kurtosis:                       1.637   Cond. No.                     1.64e+04
    ==============================================================================

    Warnings:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
    [2] The condition number is large, 1.64e+04. This might indicate that there are
    strong multicollinearity or other numerical problems.

### Normalidade dos resíduos - Omnibus

> ### $H_0:$ Os dados se distribuem como uma normal

https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.normaltest.html

```python
from scipy.stats import normaltest

normaltest(dataset.Residuos)
```

    NormaltestResult(statistic=22.832968027935664, pvalue=1.1012451024593674e-05)

```python
p_valor <= 0.05
```

    True

https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.probplot.html

```python
from scipy.stats import probplot
import matplotlib.pyplot as plt
```

```python
(_, (_,_,_)) = probplot(dataset.Residuos, plot=plt)
```

```python
dados.Altura.hist(bins=50)
```

```python
(_, (_,_,_)) = probplot(dados.Altura, plot=plt)
```

### Verificando a simetria

https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.skew.html

```python
from scipy.stats import skew
```

```python
S = skew(dataset.Residuos)
S
```

    0.07306920116444209

### Verificando a curtose

https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.kurtosis.html

```python
from scipy.stats import kurtosis
```

```python
C = 3 + kurtosis(dataset.Residuos)
C
```

    1.636962158068644

### Normalidade dos resíduos - Jarque-Bera (statsmodels)

> ### $H_0:$ Os dados se distribuem como uma normal

> ### Estatística de teste
> ### $$JB = \frac{n}{6}(S^2 + \frac{1}{4}(C - 3)^2)$$
> #### Onde,
> #### $n$ - número de observações
> #### $S$ - Coeficiente de assimetria
> #### $C$ - Coeficiente de curtose

```python
JB = (n/6) * (S**2 + (1/4)*(C-3)**2)
JB
```

    3.9150595649419317

```python
from scipy.stats import chi2

p_valor = chi2.sf(JB,2)
p_valor
```

    0.14120680197285568

```python
p_valor <= 0.05
```

    False

### Normalidade dos resíduos - Jarque-Bera (Correção)

> ### $H_0:$ Os dados se distribuem como uma normal

> ### Estatística de teste
> ### $$JB = \frac{n-k}{6}(S^2 + \frac{1}{4}(C - 3)^2)$$
> #### Onde,
> #### $n$ - número de observações
> #### $k$ - número de parâmetros do modelo
> #### $S$ - Coeficiente de assimetria
> #### $C$ - Coeficiente de curtose

```python
JB = ((n-1)/6) * (S**2 + (1/4)*(C-3)**2)
JB
```

    3.8367583736430926

```python
p_valor = chi2.sf(JB,2)
p_valor
```

    0.14684477730154344

```python
p_valor <= 0.05
```

    False

```python

```


# FYS388 Exercise 10: Spiking Neuronal Network Models

## Introduction

In this exercise, you will explore spiking networks of simple neurons. In particular, we will consider a widely studied model proposed by Nicolas Brunel about 15 years ago (N Brunel: *Dynamics of Sparsely Connected Networks of Excitatory and Inhibitory Spiking Neurons*. J Comput Neurosci **8**:183-208 (2000). [Reprint](http://dx.doi.org/10.1023/A:1008925309027)).

For details on the model structure, please see Section 2, *The Model*, of Brunel's paper. Section 3 and 4 of the paper provide the theoretical analysis and are not immediately relevant for this exercise. Section 6, in particular Figures 7 and 8 and Table 1, provide the data against which you will compare your results.

The details of the model implementation and use in NEST are described in M.-O. Gewaltig, A. Morrison, and H. E. Plesser (2014) *NEST by Example: An Introduction to the Neural Simulation Tool NEST*, which is included with NEST and also in the material for this exercise.

In Task 5, you will also investigate a network incorporating spike-timing dependent plasticity. The practical side of this is described in *NEST by Example*. For the theoretical background, see Morrison et al: *Spike-Timing-Dependent Plasticity in Balanced Random Networks*. Neural Comput **19**:1437-1467 (2007) [Reprint](http://material.bccn.uni-freiburg.de/publications-bccn/2007/Morrison07_1437.pdf), and Morrison et al: *Phenemenological models of synaptic plasticity based on spike timing*. Biol Cybern **98**:459-478 (2008) [Reprint](http://dx.doi.org/10.1007/s00422-008-0233-1).  Roughly speaking, if $\Delta t$ is the time difference between the  most recent spike of the postsynaptic neuron and the time of an incoming spike, then that spike changes the membrane potential according to (Morrison et al (2007), Eq. (2.3), with $\mu=1$):

\begin{equation}
\begin{cases}
w \leftarrow w + \lambda w e^{-|\Delta t|/{\tau_+}} & \Delta t > 0 \\
w \quad\textrm{unchanged} & \Delta t = 0 \\
w \leftarrow w - \alpha \lambda w e^{-|\Delta t|/{\tau_-}} & \Delta t < 0
\end{cases}
\end{equation}

- $\lambda$ is the learning rate
- $\alpha$ describes the balance between facilitation and depression
- In addition, weights are limited to $|w|\leq W_{\textrm{max}}$

## Task 1: Running the Brunel Model

Work in this exercise will be based on the Brunel model implementation `brunel-delta-nest.py`, which you can find among the NEST PyNEST examples. For convenience, it is also included with this exercise.

In the first task, you will get the model to run in two different ways: as a script and as a function.

As a preparatory step, open file `brunel-delta-nest.py`, find the following lines

    nest.SetStatus(espikes,[{"label": "brunel-py-ex",
                       "withtime": True,
                       "withgid": True,
                       "to_file": True}])

    nest.SetStatus(ispikes,[{"label": "brunel-py-in",
                       "withtime": True,
                       "withgid": True,
                       "to_file": True}])

and set the value of the `"to_file"` entries to `False`, so that NEST won't write spike data to file.

### Simulation 1: The original model

Run the original model once, without any changes. You should obtain output that reports to the following:

        Number of neurons : 12500
        Number of synapses: 15637600
               Exitatory  : 12512500
               Inhibitory : 3125000
        Excitatory rate   : 31.86 Hz
        Inhibitory rate   : 32.02 Hz
        Building time     : 3.14 s
        Simulation time   : 53.59 s

Building and simulation time will be different on your computer, but the number of neurons and synapses and the firing rates reported should be identical.

### Simulation 2: Converting the script to a function

Before you proceed, activate automatic reloading of modules in IPython with the following commands:

    %load_ext autoreload
    %autoreload 2

For further exploration of the model, it would be cumbersome if you had to create a new script file each time you change a parameter.  As the next step, you shall therefore turn the `brunel-delta-nest.py` script into a Python module `brunel_delta.py` defining a `sim_brunel_delta()` function that builds and simulates the model network. Note the underscore in the module name: Files that are to be loaded as Python modules cannot have hyphens in their name.

To allow for greatest flexibilty, the function should take the following parameters, all with default values from the orignial script (delete the corresponding lines in the function body):

    def sim_brunel_delta(dt=0.1,
                         simtime=1000.0,
                         delay=1.5,
                         g=5.0,
                         eta=2.0,
                         epsilon=0.1,
                         order=2500,
                         J=0.1,
                         N_rec=50,
                         num_threads=1,
                         print_report=True):

The `num_threads` parameter is new. To make use of it, find the following line

    nest.SetKernelStatus({"resolution": dt, "print_time": True})

and extend it to

    nest.SetKernelStatus({"resolution": dt, "print_time": True,
                          "local_num_threads": num_threads})

If `print_report` is true, the function should print out information about its progress and the report shown above, otherwise, it should be quiet. To suppress all output from NEST, add

    nest.set_verbosity('M_WARNING')

to your code.

Remove the `nest.raster_plot.from_device()` call from the function.

The function shall return a tuple consisting of the excitatory and inhibitory spikes recorded, as [Pandas](http://pandas.pydata.org) data frames:

    import pandas as pd

    exc_spikes = nest.GetStatus(espikes, 'events')[0]
    inh_spikes = nest.GetStatus(ispikes, 'events')[0]

    return pd.DataFrame(exc_spikes), pd.DataFrame(inh_spikes)

#### Using the function

Use the function like this:

    from brunel_delta import sim_brunel_delta

    esp, isp = sim_brunel_delta()

This should print the same report (except some variation in the building and simulation times) as Simulation 1.

For a quick check, of the results, use the `describe()` method, and look at the first few spikes:

    esp.describe()

    |       | senders     | times       |
    | ----- | ----------- | ----------- |
    | count | 1593.000000 | 1593.000000 |
    | mean  |   25.497175 |  501.450345 |
    | std   |   14.433182 |  287.833542 |
    | min   |    1.000000 |   13.200000 |
    | 25%   |   13.000000 |  249.200000 |
    | 50%   |   25.000000 |  501.600000 |
    | 75%   |   38.000000 |  751.900000 |
    | max   |   50.000000 |  998.100000 |

    esp.sort_values(by='times')[:5]

    |   | senders | times |
    | - | ------- | ----- |
    | 0 |      18 |  13.2 |
    | 1 |      11 |  13.5 |
    | 2 |      41 |  13.6 |
    | 3 |      44 |  13.8 |
    | 4 |      26 |  14.0 |

You should see exactly the same spike times.

#### Plotting the results

Now create a raster plot showing the spike times of the excitatory neurons in blue and the inhibitory neurons in red, by using `times` as x-axis values and `senders` as y-axis values.

**Hint**: Check the minimum and maximum values of the `senders` gids and offset the plotted values suitably to get a tidy graph.

Create spike time histograms, using the `plt.hist()` function with a suitable number of bins. Use `histtype=step` to combine PSTHs for excitatory and inhibitory cells.

### Simulation 3: Exploring parallel simulation

Run the same simulation as above with `simtime=250.` (to save time) with different values of `num_threads` (start with 1, 2, 4) to see if you can save time using several cores on your computer. In some cases, it actually may help to use more threads than your computer has cores. You should not do much else on your computer when running on as many or more threads than your computer has cores.

## Task 2: Reproducing Brunel

In this task, you shall try to reproduce the results presented by Brunel in his paper. In particular, you shall simulate the network for the four parameter combinations marked in Fig. 7 of the paper and see if you can obtain responses similar to those displayed in Fig. 8 of the paper.

For each of the four simulations below, you shall

- record from `N_rec=1000` neurons (each excitatory and inhibitory)
- simulate for 1 second biological time
- plot raster plots showing the responses of 40 excitatory and 10 inhibitory neurons for $800 ms < t < 1000 ms$
- plot spike-time histograms with bin width $dt=0.1$ ms for both excitatory and inhibitory neurons, for the same time interval; the histograms shall use data from all recorded neurons

Note that $\eta = \nu_{\textrm{ext}}/\nu_{\textrm{thr}}$.

Carefully compare your results with Fig 8 in Brunel (2000):

- Are your results sufficiently close to Brunel's to conclude that you have reproduced Brunel's results?
- If not, describe the differences!
- Are differences larger for some case(s) than for others?
- *Carefully* read section 2 "The Model" in the paper and compare with the simulation code.
    - Is the code a faithful implementation of the model?
    - If not, can you make the implementation faithful?

## Task 3: Turning off the input, and cranking up the weights

In this task, you will turn off the Poisson input driving all neurons in the beginning after 400 ms and continue simulation until 1 second is simulated.

To this end, modify your code so that you can set the `stop` parameter of the Poisson generators. The stopping time should be passed to `sim_brunel_delta()` as keyword argument `input_stop`.  You should also incorporate any corrections made as part of Task 2.

Plot spike rasters and histograms for the full second; use 2ms bin widths for the histograms.

1. Simulate with the parameters for case C.
1. Simulate once more, with parameters for case C, but with $J=1.0$, i.e., with tenfold synaptic weights.

1. Describe your observations and attempt to analyse their causes!
1. Why is their a highly synchronous volley of spikes at the beginning of the simulation?
1. How could you avoid that volley?

## Task 4: Recuding the network size

Simulations of the full Brunel model take more than a minute (on my machine). For fast exploration, it would therefore be attractive to simulate a smaller network. Network size is determined by the `order` parameter according to the following rule:

- $N_E = 4 \times $ `order`
- $N_I = $ `order`

1. Reduce the network size to 1/10 of the original size (i.e., `order=250`) and simulate case C using the original $J=0.1$; reduce the number of recorded neurons to 250.
1. Plot a raster plot and histogram as previously.
1. Describe the result and compare it to the full model.
1. Try to adjust parameters ($J$, $g$, $\eta$) to obtain (approximately) the same firing rate and firing pattern as for the full scale model.
    - *Note*: To avoid problems with initial transients, compute firing rates only over $500ms < t < 1000ms$.
1. Discuss your experiences!

## Task 5: A network with plastic synapses

In the final task of this exercise, explore the network with plastic excitatory-excitatory synapses. In this case, we want to stop the simulation regularly, so that we can read out the synaptic weights. We therefore need a function building the network and a separate function to simulate it.

### Preparations

*Note*: If you feel comfortable with object-oriented programming in Python, feel free to deviate from the detailed implementation suggested below and choose an approach closer to the one suggest in Sec. 8 of *NEST by Example*.

1. Add a function `build_brunel_delta_plastic()` to your `brunel_delta` module.
    1. Start from the `sim_brunel_delta()` function an remove all simulation-related parameters and code.
    1. Create a new synapse model `excitatory_plastic` and use it to connect the excitatory-excitatory connections.
    1. Excitatory-excitatory and excitatory-inhibitory synapses shall have randomized weights, uniformly drawn from $[0.5 J_E, 1.5 J_E)$. See *NEST by Example*, Sec. 6 and 7 for suggestions.
    1. The initial membrane potential of all neurons shall be randomized uniformly over $[-20 mV, 20 mV)$.
    1. The function shall also accept parameters `alpha` (default 2.02), `lambd` (default 0.01; `a` dropped to avoid conflict with keyword `lambda`), and `Wmax` (default 3.0).
    1. The function shall return the following four lists:
        1. excitatory spike detector (single element list)
        1. inhibitory spike detector (single element list)
        1. all excitatory nodes
        1. all inhibitory nodes
1. Add a function `sim_brunel_delta_plastic()` to your `brunel_delta` module.
    1. This function should be based on the simulation part of `sim_brunel_delta()`.
    1. It should take the following arguments:
        1. the simulation time
        1. the excitatory and inhibitory spike detector lists returned by the build-function
        1. a list of all excitatory neurons for which you want to collect synaptic weights (outgoing synapses)
    1. It should return the same spike-data dataframes as `sim_brunel_delta()` and in addition a NumPy array containing the weights of all outgoing synapses from the specified neurons at the end of the simulation.

### Usage

You can now use these two functions like this:

    esd, isd, enrn, inrn = build_brunel_delta_plastic(order=250, N_rec=250, g=5., eta=1.25, J=1., V_reset=10.,
                                                      lambda=0.01, alpha=2.02, Wmax=3.)
    esp, isp, e_wgts = sim_brunel_delta_plastic(200., esd, isd, enrn[:250])

In that example, we build a small network and then simulate for 200 ms. `e_wgts` will contain the weights of all outgoing synapses formed by the first 250 excitatory neurons.

The following code will simulate `n_step` times for `t_step` and collect weights after each simulation, as columns of `e_wgts`, while `t_wgts` will contain the times at which the weights were measured. `ini_wgts` are the weights at the beginning of the simulation.

    esd, isd, enrn, inrn = build_brunel_delta_plastic(order=250, N_rec=250, g=5., eta=1.25, J=1., V_reset=10.,
                                                      lambda=0.01, alpha=2., Wmax=3.)

    t_step = 200.
    n_step = 10

    ini_wgts = nest.GetStatus(nest.GetConnections(source=enrn[:250], synapse_model='excitatory_plastic'), 'weight')
    e_wgts = np.nan * np.ones((len(ini_wgts), n_step+1))
    e_wgts[:, 0] = ini_wgts
    t_wgts = t_step * np.arange(n_step+1)

    for k in range(10):
        esp, isp, e_wgts[:, k+1] = sim_brunel_delta_plastic(200., esd, isd, enrn[:250], print_report=False)

### Simulations

Perform simulations varying the learning rate $\lambda$ and the depression-facilitation-ratio $\alpha$. For each parameter combination

- simulate for 10 times 200 ms and measure the weight distribution after each 200 ms
- plot historgrams of all weight distributions in a single diagram
- plot a raster plot over the full duration of the experiment
- plot a spike-time histogram over the full duration of the experiment

Describe and discuss your observations!

```python

```


```python
# The magic happens here
import formula as f
```

## Printing formulas for visual checking

```python
from sympy import init_printing
init_printing()
```

```python
dbladd = f.xDBLADD().formula()
dbladd['X2']/dbladd['Z2'], dbladd['X+']/dbladd['Z+']
```

```python
dbl = f.xDBL().formula()
dbl['X'] / dbl['Z']
```

```python
tpl = f.xTPL().formula()
tpl['X'] / tpl['Z']
```

```python
iso2 = f.xIso2().formula()
f.expand(4 * iso2['A₂₄⁺'] / iso2['C₂₄'] - 2), iso2['X'] / iso2['Z']
```

```python
iso4 = f.xIso4().formula()
f.expand(4 * iso4['A₂₄⁺'] / iso4['C₂₄'] - 2), iso4['X'] / iso4['Z']
```

```python
iso3 = f.xIso3().formula()
f.factor(2 * (iso3['A₂₄⁺'] + iso3['A₂₄⁻']) / (iso3['A₂₄⁺'] - iso3['A₂₄⁻'])), iso3['X'] / iso3['Z']
```

## Looking for zeros

```python
print(f.xDBLADD().pprint())
```

    /**********  Combined Doubling and Differential Addition  **********/
    proc xDBLADD(X_P, Z_P, X_Q, Z_Q, X_QP, Z_QP, A₂₄⁺):
    t0:     X_P + Z_P
            X_P + Z_P
              {X_P: -Z_P}

    t1:     X_P - Z_P
            X_P - Z_P
              {X_P: Z_P}

    t2:     t0^2
            (X_P + Z_P)**2
              {X_P: -Z_P}

    t3:     X_Q - Z_Q
            X_Q - Z_Q
              {X_Q: Z_Q}

    t4:     X_Q + Z_Q
            X_Q + Z_Q
              {X_Q: -Z_Q}

    t5:     t0 * t3
            (X_P + Z_P)*(X_Q - Z_Q)
              {X_P: -Z_P}
              {X_Q: Z_Q}

    t6:     t1^2
            (X_P - Z_P)**2
              {X_P: Z_P}

    t7:     t1 * t4
            (X_P - Z_P)*(X_Q + Z_Q)
              {X_P: Z_P}
              {X_Q: -Z_Q}

    t8:     t2 - t6
            4*X_P*Z_P
              {X_P: 0}
              {Z_P: 0}

    X2>     t2 * t6
            (X_P - Z_P)**2*(X_P + Z_P)**2
              {X_P: -Z_P}
              {X_P: Z_P}

    t10:    A₂₄⁺ * t8
            4*A₂₄⁺*X_P*Z_P
              {A₂₄⁺: 0}
              {X_P: 0}
              {Z_P: 0}

    t11:    t5 - t7
            -2*(X_P*Z_Q - X_Q*Z_P)
              {X_P: X_Q*Z_P/Z_Q}

    t12:    t10 + t6
            4*A₂₄⁺*X_P*Z_P + X_P**2 - 2*X_P*Z_P + Z_P**2
              {A₂₄⁺: (X_P - Z_P)**2/((X_P - Z_P)**2 - (X_P + Z_P)**2)}

    t13:    t5 + t7
            2*(X_P*X_Q - Z_P*Z_Q)
              {X_P: Z_P*Z_Q/X_Q}

    Z2>     t12 * t8
            4*X_P*Z_P*(4*A₂₄⁺*X_P*Z_P + X_P**2 - 2*X_P*Z_P + Z_P**2)
              {A₂₄⁺: (X_P - Z_P)**2/((X_P - Z_P)**2 - (X_P + Z_P)**2)}
              {X_P: 0}
              {Z_P: 0}

    t15:    t11^2
            4*(X_P*Z_Q - X_Q*Z_P)**2
              {X_P: X_Q*Z_P/Z_Q}

    t16:    t13^2
            4*(X_P*X_Q - Z_P*Z_Q)**2
              {X_P: Z_P*Z_Q/X_Q}

    Z+>     X_QP * t15
            4*X_QP*(X_P*Z_Q - X_Q*Z_P)**2
              {X_P: X_Q*Z_P/Z_Q}
              {X_QP: 0}

    X+>     Z_QP * t16
            4*Z_QP*(X_P*X_Q - Z_P*Z_Q)**2
              {X_P: Z_P*Z_Q/X_Q}
              {Z_QP: 0}

```python
f.factor(f.xDBLADD()[12].exp)  # 2-torsion points
```

```python
print(f.xDBL().pprint())
```

    /**********  Point Doubling  **********/
    proc xDBL(X, Z, A₂₄⁺, C₂₄):
    t0:     X - Z
            X - Z
              {X: Z}

    t1:     X + Z
            X + Z
              {X: -Z}

    t2:     t0^2
            (X - Z)**2
              {X: Z}

    t3:     t1^2
            (X + Z)**2
              {X: -Z}

    t4:     C₂₄ * t2
            C₂₄*(X - Z)**2
              {C₂₄: 0}
              {X: Z}

    X>      t4 * t3
            C₂₄*(X - Z)**2*(X + Z)**2
              {C₂₄: 0}
              {X: -Z}
              {X: Z}

    t6:     t3 - t2
            4*X*Z
              {X: 0}
              {Z: 0}

    t7:     A₂₄⁺ * t6
            4*A₂₄⁺*X*Z
              {A₂₄⁺: 0}
              {X: 0}
              {Z: 0}

    t8:     t4 + t7
            4*A₂₄⁺*X*Z + C₂₄*X**2 - 2*C₂₄*X*Z + C₂₄*Z**2
              {A₂₄⁺: C₂₄*(X - Z)**2/((X - Z)**2 - (X + Z)**2)}

    Z>      t8 * t6
            4*X*Z*(4*A₂₄⁺*X*Z + C₂₄*X**2 - 2*C₂₄*X*Z + C₂₄*Z**2)
              {A₂₄⁺: C₂₄*(X - Z)**2/((X - Z)**2 - (X + Z)**2)}
              {X: 0}
              {Z: 0}

```python
print(f.xTPL().pprint())
```

    /**********  Point Tripling  **********/
    proc xTPL(X, Z, A₂₄⁺, A₂₄⁻):
    t0:     X - Z
            X - Z
              {X: Z}

    t1:     X + Z
            X + Z
              {X: -Z}

    t2:     t0^2
            (X - Z)**2
              {X: Z}

    t3:     t1^2
            (X + Z)**2
              {X: -Z}

    t4:     t1 + t0
            2*X
              0

    t5:     t1 - t0
            2*Z
              0

    t6:     t4^2
            4*X**2
              0

    t7:     t6 - t3
            (X - Z)*(3*X + Z)
              {X: -Z/3}
              {X: Z}

    t8:     t7 - t2
            2*(X - Z)*(X + Z)
              {X: -Z}
              {X: Z}

    t9:     t3 * A₂₄⁺
            A₂₄⁺*(X + Z)**2
              {A₂₄⁺: 0}
              {X: -Z}

    t10:    t9 * t3
            A₂₄⁺*(X + Z)**4
              {A₂₄⁺: 0}
              {X: -Z}

    t11:    t2 * A₂₄⁻
            A₂₄⁻*(X - Z)**2
              {A₂₄⁻: 0}
              {X: Z}

    t12:    t2 * t11
            A₂₄⁻*(X - Z)**4
              {A₂₄⁻: 0}
              {X: Z}

    t13:    t12 - t10
            -A₂₄⁺*X**4 - 4*A₂₄⁺*X**3*Z - 6*A₂₄⁺*X**2*Z**2 - 4*A₂₄⁺*X*Z**3 - A₂₄⁺*Z**4 + A₂₄⁻*X**4 - 4*A₂₄⁻*X**3*Z + 6*A₂₄⁻*X**2*Z**2 - 4*A₂₄⁻*X*Z**3 + A₂₄⁻*Z**4
              {A₂₄⁺: A₂₄⁻*(X - Z)**4/(X + Z)**4}

    t14:    t9 - t11
            A₂₄⁺*X**2 + 2*A₂₄⁺*X*Z + A₂₄⁺*Z**2 - A₂₄⁻*X**2 + 2*A₂₄⁻*X*Z - A₂₄⁻*Z**2
              {A₂₄⁺: A₂₄⁻*(X - Z)**2/(X + Z)**2}

    t15:    t14 * t8
            2*(X - Z)*(X + Z)*(A₂₄⁺*X**2 + 2*A₂₄⁺*X*Z + A₂₄⁺*Z**2 - A₂₄⁻*X**2 + 2*A₂₄⁻*X*Z - A₂₄⁻*Z**2)
              {A₂₄⁺: A₂₄⁻*(X - Z)**2/(X + Z)**2}
              {X: -Z}
              {X: Z}

    t16:    t13 + t15
            A₂₄⁺*X**4 - 6*A₂₄⁺*X**2*Z**2 - 8*A₂₄⁺*X*Z**3 - 3*A₂₄⁺*Z**4 - A₂₄⁻*X**4 + 6*A₂₄⁻*X**2*Z**2 - 8*A₂₄⁻*X*Z**3 + 3*A₂₄⁻*Z**4
              {A₂₄⁺: A₂₄⁻*(X - Z)**2*(-4*X**2 + 2*(X - Z)**2 + (X + Z)**2)/((X + Z)**2*(-4*X**2 + (X - Z)**2 + 2*(X + Z)**2))}

    t17:    t16^2
            (A₂₄⁺*X**4 - 6*A₂₄⁺*X**2*Z**2 - 8*A₂₄⁺*X*Z**3 - 3*A₂₄⁺*Z**4 - A₂₄⁻*X**4 + 6*A₂₄⁻*X**2*Z**2 - 8*A₂₄⁻*X*Z**3 + 3*A₂₄⁻*Z**4)**2
              {A₂₄⁺: A₂₄⁻*(X - Z)**3*(X + 3*Z)/((X - 3*Z)*(X + Z)**3)}

    X>      t17 * t4
            2*X*(A₂₄⁺*X**4 - 6*A₂₄⁺*X**2*Z**2 - 8*A₂₄⁺*X*Z**3 - 3*A₂₄⁺*Z**4 - A₂₄⁻*X**4 + 6*A₂₄⁻*X**2*Z**2 - 8*A₂₄⁻*X*Z**3 + 3*A₂₄⁻*Z**4)**2
              {A₂₄⁺: A₂₄⁻*(X - Z)**3*(X + 3*Z)/((X - 3*Z)*(X + Z)**3)}
              {X: 0}

    t19:    t13 - t15
            -3*A₂₄⁺*X**4 - 8*A₂₄⁺*X**3*Z - 6*A₂₄⁺*X**2*Z**2 + A₂₄⁺*Z**4 + 3*A₂₄⁻*X**4 - 8*A₂₄⁻*X**3*Z + 6*A₂₄⁻*X**2*Z**2 - A₂₄⁻*Z**4
              {A₂₄⁺: A₂₄⁻*(X - Z)**2*(4*X**2 - (X + Z)**2)/((X + Z)**2*(4*X**2 - (X - Z)**2))}

    t20:    t19^2
            (3*A₂₄⁺*X**4 + 8*A₂₄⁺*X**3*Z + 6*A₂₄⁺*X**2*Z**2 - A₂₄⁺*Z**4 - 3*A₂₄⁻*X**4 + 8*A₂₄⁻*X**3*Z - 6*A₂₄⁻*X**2*Z**2 + A₂₄⁻*Z**4)**2
              {A₂₄⁺: A₂₄⁻*(X - Z)**3*(3*X + Z)/((X + Z)**3*(3*X - Z))}

    Z>      t20 * t5
            2*Z*(3*A₂₄⁺*X**4 + 8*A₂₄⁺*X**3*Z + 6*A₂₄⁺*X**2*Z**2 - A₂₄⁺*Z**4 - 3*A₂₄⁻*X**4 + 8*A₂₄⁻*X**3*Z - 6*A₂₄⁻*X**2*Z**2 + A₂₄⁻*Z**4)**2
              {A₂₄⁺: A₂₄⁻*(X - Z)**3*(3*X + Z)/((X + Z)**3*(3*X - Z))}
              {Z: 0}

Potentially dangerous intermediate values. If $(X:Z)$ has order $3$, then $A = -2$, but for higher orders it's unclear what happens.

```python
f.factor(f.xTPL()[7].exp)
```

$t_{13}$ and $t_{16}$ are interesting. If we assume that $(X:Z)$ has order $3^n$, the only zeros seem to be $A=±2$ (with high multiplicity), as the Sage experiment below shows:

```
sage: R.<A,X> = PolynomialRing(QQ, order='invlex')
sage: R.ideal(2*A*X*(X^2+1) + X^4 + 6*X^2 + 1,
              EllipticCurve([0,A,0,1,0]).division_polynomial(27, X)).groebner_basis()[-1].factor()
(A - 2)^69 * (A + 2)^69
sage: R.ideal(4*A*X - X^4 + 6*X^2 + 3,
              EllipticCurve([0,A,0,1,0]).division_polynomial(27, X)).groebner_basis()[-1].factor()
(A - 2)^81 * (A + 2)^81
```

```python
f.xTPL()[13].exp
```

```python
A = f.Symbol('A')
f.factor(f.xTPL()[13].exp.subs({'A₂₄⁺':(A+2), 'A₂₄⁻':(A-2), 'Z':1}))
```

```python
f.factor(f.xTPL()[16].exp.subs({'A₂₄⁺':(A+2), 'A₂₄⁻':(A-2), 'Z':1}))
```

This is just the 3-division polynomial, nothing to see here

```python
f.factor(f.xTPL()[19].exp.subs({'A₂₄⁺':(A+2), 'A₂₄⁻':(A-2), 'Z':1}))  # 3-division polynomial
```

```python
print(f.xIso2().pprint())
```

    /**********  Degree 2 isogeny computation and evaluation  **********/
    proc xIso2(X₂, Z₂, X_P, Z_P):
    t0:     X₂^2
            X₂**2
              0

    C₂₄>    Z₂^2
            Z₂**2
              0

    A₂₄⁺>   C₂₄ - t0
            -(X₂ - Z₂)*(X₂ + Z₂)
              {X₂: -Z₂}
              {X₂: Z₂}

    t1:     X₂ + Z₂
            X₂ + Z₂
              {X₂: -Z₂}

    t2:     X₂ - Z₂
            X₂ - Z₂
              {X₂: Z₂}

    t3:     X_P + Z_P
            X_P + Z_P
              {X_P: -Z_P}

    t4:     X_P - Z_P
            X_P - Z_P
              {X_P: Z_P}

    t5:     t1 * t4
            (X_P - Z_P)*(X₂ + Z₂)
              {X_P: Z_P}
              {X₂: -Z₂}

    t6:     t2 * t3
            (X_P + Z_P)*(X₂ - Z₂)
              {X_P: -Z_P}
              {X₂: Z₂}

    t7:     t5 + t6
            2*(X_P*X₂ - Z_P*Z₂)
              {X_P: Z_P*Z₂/X₂}

    t8:     t5 - t6
            2*(X_P*Z₂ - X₂*Z_P)
              {X_P: X₂*Z_P/Z₂}

    X>      X_P * t7
            2*X_P*(X_P*X₂ - Z_P*Z₂)
              {X_P: 0}
              {X₂: Z_P*Z₂/X_P}

    Z>      Z_P * t8
            2*Z_P*(X_P*Z₂ - X₂*Z_P)
              {X_P: X₂*Z_P/Z₂}
              {Z_P: 0}

```python
print(f.xIso4().pprint())
```

    /**********  Degree 4 isogeny computation and evaluation  **********/
    proc xIso4(X₄, Z₄, X_P, Z_P):
    K₂>     X₄ - Z₄
            X₄ - Z₄
              {X₄: Z₄}

    K₃>     X₄ + Z₄
            X₄ + Z₄
              {X₄: -Z₄}

    t0:     Z₄^2
            Z₄**2
              0

    t1:     t0 + t0
            2*Z₄**2
              0

    C₂₄>    t1^2
            4*Z₄**4
              0

    K₁>     t1 + t1
            4*Z₄**2
              0

    t2:     X₄^2
            X₄**2
              0

    t3:     t2 + t2
            2*X₄**2
              0

    A₂₄⁺>   t3^2
            4*X₄**4
              0

    t4:     X_P + Z_P
            X_P + Z_P
              {X_P: -Z_P}

    t5:     X_P - Z_P
            X_P - Z_P
              {X_P: Z_P}

    t6:     t4 * K₂
            (X_P + Z_P)*(X₄ - Z₄)
              {X_P: -Z_P}
              {X₄: Z₄}

    t7:     t5 * K₃
            (X_P - Z_P)*(X₄ + Z₄)
              {X_P: Z_P}
              {X₄: -Z₄}

    t8:     t4 * t5
            (X_P - Z_P)*(X_P + Z_P)
              {X_P: -Z_P}
              {X_P: Z_P}

    t9:     t8 * K₁
            4*Z₄**2*(X_P - Z_P)*(X_P + Z_P)
              {X_P: -Z_P}
              {X_P: Z_P}
              {Z₄: 0}

    t10:    t6 + t7
            2*(X_P*X₄ - Z_P*Z₄)
              {X_P: Z_P*Z₄/X₄}

    t11:    t6 - t7
            -2*(X_P*Z₄ - X₄*Z_P)
              {X_P: X₄*Z_P/Z₄}

    t12:    t10^2
            4*(X_P*X₄ - Z_P*Z₄)**2
              {X_P: Z_P*Z₄/X₄}

    t13:    t11^2
            4*(X_P*Z₄ - X₄*Z_P)**2
              {X_P: X₄*Z_P/Z₄}

    t14:    t9 + t12
            4*X_P*(X_P*X₄**2 + X_P*Z₄**2 - 2*X₄*Z_P*Z₄)
              {X_P: 0}
              {X_P: 2*X₄*Z_P*Z₄/(X₄**2 + Z₄**2)}

    t15:    t13 - t9
            -4*Z_P*(2*X_P*X₄*Z₄ - X₄**2*Z_P - Z_P*Z₄**2)
              {X_P: Z_P*(X₄**2 + Z₄**2)/(2*X₄*Z₄)}
              {Z_P: 0}

    X>      t14 * t12
            16*X_P*(X_P*X₄ - Z_P*Z₄)**2*(X_P*X₄**2 + X_P*Z₄**2 - 2*X₄*Z_P*Z₄)
              {X_P: 0}
              {X_P: Z_P*Z₄/X₄}
              {X_P: 2*X₄*Z_P*Z₄/(X₄**2 + Z₄**2)}

    Z>      t13 * t15
            -16*Z_P*(X_P*Z₄ - X₄*Z_P)**2*(2*X_P*X₄*Z₄ - X₄**2*Z_P - Z_P*Z₄**2)
              {X_P: X₄*Z_P/Z₄}
              {X_P: Z_P*(X₄**2 + Z₄**2)/(2*X₄*Z₄)}
              {Z_P: 0}

These values seem to be related to the kernel and its coset by $T=(0:1)$, so probably no big deal

```python
f.solve(f.xIso4()[14].exp.subs({'Z₄':1, 'Z_P':1}))
```

```python
f.solve(f.xIso4()[15].exp.subs({'Z₄':1, 'Z_P':1}))
```

```python
f.solve(f.xIso4()['X'].exp.subs({'Z₄':1, 'Z_P':1}))
```

```python
f.solve(f.xIso4()['Z'].exp.subs({'Z₄':1, 'Z_P':1}))
```

```python
print(f.xIso3().pprint())
```

    /**********  Degree 3 isogeny computation and evaluation  **********/
    proc xIso3(X₃, Z₃, X_P, Z_P):
    K₁>     X₃ - Z₃
            X₃ - Z₃
              {X₃: Z₃}

    t0:     K₁^2
            (X₃ - Z₃)**2
              {X₃: Z₃}

    K₂>     X₃ + Z₃
            X₃ + Z₃
              {X₃: -Z₃}

    t1:     K₂^2
            (X₃ + Z₃)**2
              {X₃: -Z₃}

    t2:     t0 + t1
            2*(X₃**2 + Z₃**2)
              {X₃: -I*Z₃}
              {X₃: I*Z₃}

    t3:     K₁ + K₂
            2*X₃
              0

    t4:     t3^2
            4*X₃**2
              0

    t5:     t4 - t2
            2*(X₃ - Z₃)*(X₃ + Z₃)
              {X₃: -Z₃}
              {X₃: Z₃}

    t6:     t1 + t5
            (X₃ + Z₃)*(3*X₃ - Z₃)
              {X₃: -Z₃}
              {X₃: Z₃/3}

    t7:     t5 + t0
            (X₃ - Z₃)*(3*X₃ + Z₃)
              {X₃: -Z₃/3}
              {X₃: Z₃}

    t8:     t7 + t0
            4*X₃*(X₃ - Z₃)
              {X₃: 0}
              {Z₃: X₃}

    t9:     t8 + t8
            8*X₃*(X₃ - Z₃)
              {X₃: 0}
              {Z₃: X₃}

    t10:    t1 + t9
            (3*X₃ - Z₃)**2
              {X₃: Z₃/3}

    A₂₄⁻>   t6 * t10
            (X₃ + Z₃)*(3*X₃ - Z₃)**3
              {X₃: -Z₃}
              {X₃: Z₃/3}

    t11:    t1 + t6
            4*X₃*(X₃ + Z₃)
              {X₃: 0}
              {Z₃: -X₃}

    t12:    t11 + t11
            8*X₃*(X₃ + Z₃)
              {X₃: 0}
              {Z₃: -X₃}

    t13:    t0 + t12
            (3*X₃ + Z₃)**2
              {X₃: -Z₃/3}

    A₂₄⁺>   t7 * t13
            (X₃ - Z₃)*(3*X₃ + Z₃)**3
              {X₃: -Z₃/3}
              {X₃: Z₃}

    t14:    X_P + Z_P
            X_P + Z_P
              {X_P: -Z_P}

    t15:    X_P - Z_P
            X_P - Z_P
              {X_P: Z_P}

    t16:    K₁ * t14
            (X_P + Z_P)*(X₃ - Z₃)
              {X_P: -Z_P}
              {X₃: Z₃}

    t17:    K₂ * t15
            (X_P - Z_P)*(X₃ + Z₃)
              {X_P: Z_P}
              {X₃: -Z₃}

    t18:    t16 + t17
            2*(X_P*X₃ - Z_P*Z₃)
              {X_P: Z_P*Z₃/X₃}

    t19:    t17 - t16
            2*(X_P*Z₃ - X₃*Z_P)
              {X_P: X₃*Z_P/Z₃}

    t20:    t18^2
            4*(X_P*X₃ - Z_P*Z₃)**2
              {X_P: Z_P*Z₃/X₃}

    t21:    t19^2
            4*(X_P*Z₃ - X₃*Z_P)**2
              {X_P: X₃*Z_P/Z₃}

    X>      X_P * t20
            4*X_P*(X_P*X₃ - Z_P*Z₃)**2
              {X_P: 0}
              {X_P: Z_P*Z₃/X₃}

    Z>      Z_P * t21
            4*Z_P*(X_P*Z₃ - X₃*Z_P)**2
              {X_P: X₃*Z_P/Z₃}
              {Z_P: 0}

Interesting, $(±i:1)$ might be a problem. These are points of order $3$ when $A=±i$ (not a CM curve, not supersingular for p434). Seems messy for higher orders.

```python
f.factor(f.xIso3()[2].exp)
```

$±1/3$, again!

```python
f.factor(f.xIso3()[6].exp)
```

```python
f.factor(f.xIso3()[7].exp)
```

Careful about translation by $T=(0:1)$. Not a big deal, but a bit surprised to see this here.

```python
f.factor(f.xIso3()[18].exp)
```