# Lambda School Data Science Module 143

## Introduction to Bayesian Inference

!['Detector! What would the Bayesian statistician say if I asked him whether the--' [roll] 'I AM A NEUTRINO DETECTOR, NOT A LABYRINTH GUARD. SERIOUSLY, DID YOUR BRAIN FALL OUT?' [roll] '... yes.'](https://imgs.xkcd.com/comics/frequentists_vs_bayesians.png)

*[XKCD 1132](https://www.xkcd.com/1132/)*

## Prepare - Bayes' Theorem and the Bayesian mindset

Bayes' theorem possesses a near-mythical quality - a bit of math that somehow magically evaluates a situation. But this mythicalness has more to do with its reputation and advanced applications than the actual core of it - deriving it is actually remarkably straightforward.

### The Law of Total Probability

By definition, the total probability of all outcomes (events) if some variable (event space) $A$ is 1. That is:

$$P(A) = \sum_n P(A_n) = 1$$

The law of total probability takes this further, considering two variables ($A$ and $B$) and relating their marginal probabilities (their likelihoods considered independently, without reference to one another) and their conditional probabilities (their likelihoods considered jointly). A marginal probability is simply notated as e.g. $P(A)$, while a conditional probability is notated $P(A|B)$, which reads "probability of $A$ *given* $B$".

The law of total probability states:

$$P(A) = \sum_n P(A | B_n) P(B_n)$$

In words - the total probability of $A$ is equal to the sum of the conditional probability of $A$ on any given event $B_n$ times the probability of that event $B_n$, and summed over all possible events in $B$.

### The Law of Conditional Probability

What's the probability of something conditioned on something else? To determine this we have to go back to set theory and think about the intersection of sets:

The formula for actual calculation:

$$P(A|B) = \frac{P(A \cap B)}{P(B)}$$

Think of the overall rectangle as the whole probability space, $A$ as the left circle, $B$ as the right circle, and their intersection as the red area. Try to visualize the ratio being described in the above formula, and how it is different from just the $P(A)$ (not conditioned on $B$).

We can see how this relates back to the law of total probability - multiply both sides by $P(B)$ and you get $P(A|B)P(B) = P(A \cap B)$ - replaced back into the law of total probability we get $P(A) = \sum_n P(A \cap B_n)$.

This may not seem like an improvement at first, but try to relate it back to the above picture - if you think of sets as physical objects, we're saying that the total probability of $A$ given $B$ is all the little pieces of it intersected with $B$, added together. The conditional probability is then just that again, but divided by the probability of $B$ itself happening in the first place.

\begin{align}
P(A|B) &= \frac{P(A \cap B)}{P(B)}\\
\Rightarrow P(A|B)P(B) &= P(A \cap B)\\
P(B|A) &= \frac{P(B \cap A)}{P(A)}\\
\Rightarrow P(B|A)P(A) &= P(B \cap A)\\
\Rightarrow P(A|B)P(B) &= P(B|A)P(A) \\
P(A \cap B) &= P(B \cap A)\\
P(A|B) &= \frac{P(B|A) \times P(A)}{P(B)}
\end{align}

### Bayes Theorem

Here is is, the seemingly magic tool:

$$P(A|B) = \frac{P(B|A)P(A)}{P(B)}$$

In words - the probability of $A$ conditioned on $B$ is the probability of $B$ conditioned on $A$, times the probability of $A$ and divided by the probability of $B$. These unconditioned probabilities are referred to as "prior beliefs", and the conditioned probabilities as "updated."

Why is this important? Scroll back up to the XKCD example - the Bayesian statistician draws a less absurd conclusion because their prior belief in the likelihood that the sun will go nova is extremely low. So, even when updated based on evidence from a detector that is $35/36 = 0.972$ accurate, the prior belief doesn't shift enough to change their overall opinion.

There's many examples of Bayes' theorem - one less absurd example is to apply to [breathalyzer tests](https://www.bayestheorem.net/breathalyzer-example/). You may think that a breathalyzer test that is 100% accurate for true positives (detecting somebody who is drunk) is pretty good, but what if it also has 8% false positives (indicating somebody is drunk when they're not)? And furthermore, the rate of drunk driving (and thus our prior belief)  is 1/1000.

What is the likelihood somebody really is drunk if they test positive? Some may guess it's 92% - the difference between the true positives and the false positives. But we have a prior belief of the background/true rate of drunk driving. Sounds like a job for Bayes' theorem!

$$
\begin{aligned}
P(Drunk | Positive) &= \frac{P(Positive | Drunk)P(Drunk)}{P(Positive)} \\
&= \frac{1 \times 0.001}{0.08} \\
&= 0.0125
\end{aligned}
$$

In other words, the likelihood that somebody is drunk given they tested positive with a breathalyzer in this situation is only 1.25% - probably much lower than you'd guess. This is why, in practice, it's important to have a repeated test to confirm (the probability of two false positives in a row is $0.08 * 0.08 = 0.0064$, much lower), and Bayes' theorem has been relevant in court cases where proper consideration of evidence was important.

Source: <https://en.wikipedia.org/wiki/Bayes%27_theorem>

## Live Lecture - Deriving Bayes' Theorem, Calculating Bayesian Confidence

Notice that $P(A|B)$ appears in the above laws - in Bayesian terms, this is the belief in $A$ updated for the evidence $B$. So all we need to do is solve for this term to derive Bayes' theorem. Let's do it together!

```
# Activity 2 - Use SciPy to calculate Bayesian confidence intervals
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.bayes_mvs.html#scipy.stats.bayes_mvs
```

```
from scipy import stats
import numpy as np

np.random.seed(seed=42)

coinflips = np.random.binomial(n=1, p=.5, size=100)
print(coinflips)
```

    [0 1 1 1 0 0 0 1 1 1 0 1 1 0 0 0 0 1 0 0 1 0 0 0 0 1 0 1 1 0 1 0 0 1 1 1 0
     0 1 0 0 0 0 1 0 1 0 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 1 0 0 1 0 1 0 1 1 0 0 1
     1 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 1 1 1 1 0 1 0 0 0]

```
def confidence_interval(data, confidence=.95):
  n = len(data)
  mean = sum(data)/n
  data = np.array(data)
  stderr = stats.sem(data)
  interval = stderr * stats.t.ppf((1 + confidence) / 2.0, n-1)
  return (mean , mean-interval, mean+interval)
```

```
confidence_interval(coinflips, confidence=.95)
```

    (0.47, 0.3704689875017368, 0.5695310124982632)

```
mean_CI, _, _ = stats.bayes_mvs(coinflips, alpha=.95)

mean_CI
```

    Mean(statistic=0.47, minmax=(0.37046898750173674, 0.5695310124982632))

```
??stats.bayes_mvs
```

```
coinflips_mean_dist, _, _ = stats.mvsdist(coinflips)
coinflips_mean_dist
```

    <scipy.stats._distn_infrastructure.rv_frozen at 0x7f38d4c9f390>

```
coinflips_mean_dist.rvs(1000)
```

## Assignment - Code it up!

Most of the above was pure math - now write Python code to reproduce the results! This is purposefully open ended - you'll have to think about how you should represent probabilities and events. You can and should look things up, and as a stretch goal - refactor your code into helpful reusable functions!

Specific goals/targets:

1. Write a function `def prob_drunk_given_positive(prob_drunk_prior, prob_positive, prob_positive_drunk)` that reproduces the example from lecture, and use it to calculate and visualize a range of situations
2. Explore `scipy.stats.bayes_mvs` - read its documentation, and experiment with it on data you've tested in other ways earlier this week
3. Create a visualization comparing the results of a Bayesian approach to a traditional/frequentist approach
4. In your own words, summarize the difference between Bayesian and Frequentist statistics

If you're unsure where to start, check out [this blog post of Bayes theorem with Python](https://dataconomy.com/2015/02/introduction-to-bayes-theorem-with-python/) - you could and should create something similar!

Stretch goals:

- Apply a Bayesian technique to a problem you previously worked (in an assignment or project work) on from a frequentist (standard) perspective
- Check out [PyMC3](https://docs.pymc.io/) (note this goes beyond hypothesis tests into modeling) - read the guides and work through some examples
- Take PyMC3 further - see if you can build something with it!

```
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
```

```
# TODO - code!

def prob_drunk_given_positive(prob_drunk_prior, prob_positive, prob_positive_drunk):
  # reproduces the example from lecture, and use it to calculate and visualize a range of situations
  return prob_positive_drunk * prob_drunk_prior / prob_positive
```

```
i = 1
df = pd.DataFrame(columns=['index','post_prob'])
index = []
post_list = []
```

```
def prob_drunk_positive_recursive(prob_drunk_prior, false_positive, true_positive, n):
#   global result
  post_prob = true_positive * prob_drunk_prior / (false_positive + prob_drunk_prior)

  global i
  index.append(int(i))
  post_list.append(post_prob)
#   print(i, post_prob)
  i += 1

  while i < n:
    prob_drunk_positive_recursive(post_prob, false_positive, true_positive, n)
#   return result

```

```
prob_drunk_positive_recursive(.001, .08, 1, 11)
```

```
index, post_list
```

    ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
     [0.012345679012345678,
      0.1336898395721925,
      0.6256256256256255,
      0.8866254326732111,
      0.9172378490200085,
      0.9197784158727866,
      0.9199822693409904,
      0.9199985815221288,
      0.9199998865216094,
      0.9199999909217278])

```
df['index'] = np.array(index)
df['post_prob'] = np.array(post_list)
```

```
df.head(10)
```

      index
      post_prob

      0
      1
      0.012346

      1
      2
      0.133690

      2
      3
      0.625626

      3
      4
      0.886625

      4
      5
      0.917238

      5
      6
      0.919778

      6
      7
      0.919982

      7
      8
      0.919999

      8
      9
      0.920000

      9
      10
      0.920000

```
df = df[df['index'] <= 10]
df.shape
```

    (0, 2)

```
plt.style.use('fivethirtyeight')
fig, ax = plt.subplots(figsize=(8,6))
plt.ylim(-.05,1)

ax.axhline(y=0, color='black', linewidth=1.5, alpha=1)
ax.axhline(y=.92, color='black', linewidth=1.5, alpha=.5)
ax.axvline(x=0, color='black', linewidth=1.5, alpha=1)
ax.axvline(x=5, color='black', linewidth=1.5, alpha=.5)
plt.plot(df['index'], df['post_prob'], linestyle='--', marker='o', linewidth=2.0)

ax.set_yticks([0, .25, .50, .75, 1])
ax.set_yticklabels(labels=['0','25','50','75','100%'], fontsize=14)
```

After multiple instances of repeating the experiment, the end result % being nearly identical to one calculated using the frequentist approach. So the confidence interval visualizations of a bayesian confidence interval look identical their frequentist counterpart.

## Resources

- [Worked example of Bayes rule calculation](https://en.wikipedia.org/wiki/Bayes'_theorem#Examples) (helpful as it fully breaks out the denominator)
- [Source code for mvsdist in scipy](https://github.com/scipy/scipy/blob/90534919e139d2a81c24bf08341734ff41a3db12/scipy/stats/morestats.py#L139)


Introducción al estudio del Método de los Elementos Finitos

Una aproximación al análisis estático lineal

Sesion 01 - Introducción

 Text provided under a Creative Commons Attribution license, CC-BY. All code is made available under the FSF-approved MIT license.(c) Carlos Alberto Alvarez Henao

***

***Docente:*** Carlos Alberto Álvarez Henao, I.C. D.Sc.

***e-mail:*** carlosalvarezh@gmail.com

***skype:*** carlos.alberto.alvarez.henao

***Herramienta:*** [Jupyter Notebook](http://jupyter.org/)

***Kernel:*** Python 3.8

***MEDELLÍN - COLOMBIA***

***2021/02***

***

Tabla de Contenidos

1  Introducción

1.1  Mecánica

1.1.1  Modelación y Simulación Computacional

1.1.2  Discretización

1.1.3  Qué es un Elemento Finito?

1.2  Qué es el FEM?

1.2.1  FEM o FEA?

1.2.2  Aplicaciones del FEM

1.2.3  Ecuaciones Diferenciales en Derivadas Parciales

1.2.4  Cómo trabaja el FEM?

1.2.5  Historia del FEM

1.3  Diferentes tipos del FEM

1.3.1  Método de elementos finitos extendido (XFEM)

1.3.2  Método de elementos finitos generalizado (GFEM)

1.3.3  Método mixto de elementos finitos

1.3.4  Método de elementos finitos hp

1.3.5  Método de elementos finitos de Galerkin discontinuo (DG-FEM)

1.4  El proceso de análisis FEM

1.4.1  Modelado

1.4.2  Simulación

1.4.3  Terminología FEM

1.5  Pasos en la solución por el FEM

1.5.1  Discretizar la estructura en elementos.

1.5.2  Determinar la matriz de rigidez local para cada elemento

1.5.3  Ensamblar la matriz de rigidez global

1.5.4  Cálcular el vector de fuerza aplicada

1.5.5  Aplicar condiciones de contorno y resolver los desplazamientos nodales

1.5.6  Resolver las reacciones externas

1.5.7  Resolver las fuerzas nodales

1.5.8  Resolver las tensiones

1.6  Atributos de elementos

1.7  Aspectos computacionales del FEM

## Introducción

### Mecánica

La [Mecánica](https://en.wikipedia.org/wiki/Mechanics) es el área de la física que estudia el movimiento y reposo de los cuerpos, y su evolución con el tiempo, más específicamente la relación entre fuerza, materia y movimiento. Las fuerzas aplicadas a los objetos dan como resultados desplazamientos o cambios de la posición de un objeto en relación con su entorno.

Los problemas a ser tratados en este curso se enmarcan dentro de la [Mecánica computacional](https://en.wikipedia.org/wiki/Computational_mechanics), análisis estático lineal, como se observa en el siguiente esquema:

\begin{equation*}
\text{Mecánica} \left \{
\begin{aligned}
&\text{Teórica} \\
&\text{Computacional} \left \{
\begin{aligned}
&\text{Nano/micro} \\
&\text{Continuo} \left \{
\begin{aligned}
&\text{Fluidos} \\
&\text{Sólidos} \left \{
\begin{aligned}
&\text{Estática} \left \{
\begin{aligned}
&\text{Lineal} \\
&\text{No Lineal}
\end{aligned}
\right.
\\
&\text{Dinámica}
\end{aligned}
\right.
\\
&\text{Multifísica}
\end{aligned}
\right.
\\
&\text{Sistema}
\end{aligned}
\right.
\\
&\text{Aplicada}
\end{aligned}
\right.
\end{equation*}

#### Modelación y Simulación Computacional

La Ciencia Computacional e Ingeniería, es una disciplina que concierne al uso de métodos y dispositivos computacionales para simular eventos físicos y sistemas en ingeniería.

#### Discretización

El proceso de [discretización](https://en.wikipedia.org/wiki/Discretization) consiste en transformar un modelo continuo en un finito número de componentes discretos que pueden ser procesados por una máquina. El [modelo Computacional](https://en.wikipedia.org/wiki/Computational_model) es la versión discretizada de un modelo matemático que se ha desarrollado para ser implementado en una máquina de cómputo. Esta discplina incluye la [modelación matemática](https://en.wikipedia.org/wiki/Mathematical_model), que es un conjunto de ecuaciones algebraicas, diferenciales y/o integrales, que gobiernan un fenómeno físico de un sistema en particular. El modelo se basa en una serie de suposiciones y restricciones impuestas al fenómeno y de las leyes físicas gobernantes.

Algunos métodos empleados en la discretización espacial son:

- ***[Elementos Finitos](https://en.wikipedia.org/wiki/Finite_element_method) (FEM)***

- ***[Elementos de Contorno](https://en.wikipedia.org/wiki/Boundary_element_method) (BEM)***

- ***[Diferencias Finitas](https://en.wikipedia.org/wiki/Finite_difference_method) (FDM)***

- ***[Spectral](https://en.wikipedia.org/wiki/Spectral_method)***

- ***[Libres de Malla](https://en.wikipedia.org/wiki/Meshfree_methods) (Mesfree)***

Cada uno de ellos presenta ventajas y desventajas que deberán ser aprovechadas según el tipo de problema a resolver. En este curso nos centraremos únicamente en el ***Método de los Elementos Finitos (FEM)***.

#### Qué es un *Elemento Finito*?

Para captar la idea de lo que es un elemento finito, veamos un ejemplo en donde se calcula el valor de $\pi$ empleando el método propuesto originalmente por [*Arquimedes*](https://en.wikipedia.org/wiki/Approximations_of_%CF%80). Él sabía que la relación entre la circunferencia de un círculo y su diámetro era $\pi$. Tenía la idea de que podía dibujar un polígono regular inscrito dentro del círculo para aproximarse a $\pi$, y cuantos más lados dibujara en el polígono, mejor aproximación a $\pi$ obtendría. En matemáticas modernas, diríamos que como el número de lados del polígono tiende a infinito, la circunferencia tiende a $2\pi$. Sin embargo, en lugar de dibujar el polígono, Arquímedes calculó la longitud usando un argumento geométrico, como el siguiente:

Dibuja un círculo de radio $1$ centrado en $A$. Inscribe un polígono de $N$ lados dentro de él. Nuestra estimación de $\pi$ es la mitad de la circunferencia del polígono (la circunferencia de un círculo es $2\pi r$, $r=1$, lo que da $2\pi$). A medida que los lados del polígono se hacen cada vez más pequeños, la circunferencia se acerca cada vez más a $2\pi$.

El diagrama muestra un segmento del polígono $ACE$. El lado del polígono $CE$ tiene una longitud $d_n$. Suponiendo que conocemos $d$ para un polígono de $N$ lados, si podemos encontrar una expresión para la longitud $CD=d_{2n}$, la longitud del borde de un polígono con $2N$ lados, solo en términos de $d_n$, entonces podemos mejorar nuestra estimación de $\pi$. Intentemos hacer eso.

Bisecamos el triángulo $CAE$ para hacer que $CAD$ y $DAE$ sean los dos nuevos segmentos iguales del polígono de $2N$ lados. Usando el teorema de Pitágoras en el triángulo rectángulo $ABC$, tenemos

$$AB^2+BC^2=1^2=1$$
$$AB=\sqrt{1+BC^2}$$

dado que

$$BC=\frac{d_n}{2}$$

sustituyendo,

$$AB=\sqrt{1-\left( \frac{d_n}{2}\right)^2}$$
$$BD=1-AB=1-\sqrt{1-\frac{d^2_n}{4}}$$

Usando el teorema de Pitágoras en el triángulo rectángulo $CBD$

$$CD^2=BC^2+BD^2$$

reemplazando

$$CD=d_{2n}=\sqrt{2-2\sqrt{1-\frac{d^2_n}{4}}}$$

$d_{2n}$ es la longitud de un lado del polígono con $2N$ lados.

Esto significa que si conocemos la longitud de los lados de un polígono con $N$ lados, entonces podemos calcular la longitud de los lados de un polígono con $2N$ lados. ¿Qué significa esto? Comencemos con un cuadrado. La inscripción de un cuadrado en un círculo se ve así, con la longitud del lado $\sqrt{2}$. Esto da una estimación de $\pi$ como $2\sqrt{2}$, que es pobre ($\approx2.828$) pero es solo el comienzo del proceso.

Podemos calcular la longitud del lado de un octágono, a partir de la longitud del lado de un cuadrado, y la longitud del lado de un $16$-gono a partir de un octágono, etc.

```python
def piArchimedes(n):
    """
    Calculate n iterations of Archimedes PI recurrence relation
    """
    polygon_edge = 2.0
    polygon_sides = 4
    for i in range(n):
        polygon_edge = 2 - 2 * math.sqrt(1 - polygon_edge / 4)
        polygon_sides *= 2
    return polygon_sides * math.sqrt(polygon_edge) / 2
```

```python
import math

print("n \t pi approx \t Error(%)")
print("1 \t {0:6.8f} \t -".format(0, ))

for n in range(16):
    result = piArchimedes(n)
    error = abs(result - math.pi) / math.pi
    print("{0} \t {1:4.8f} \t {2:4.8f}".format(n, result, error))

```

    n 	 pi approx 	 Error(%)
    1 	 0.00000000 	 -
    0 	 2.82842712 	 0.09968368
    1 	 3.06146746 	 0.02550464
    2 	 3.12144515 	 0.00641315
    3 	 3.13654849 	 0.00160561
    4 	 3.14033116 	 0.00040155
    5 	 3.14127725 	 0.00010040
    6 	 3.14151380 	 0.00002510
    7 	 3.14157294 	 0.00000627
    8 	 3.14158773 	 0.00000157
    9 	 3.14159142 	 0.00000039
    10 	 3.14159235 	 0.00000010
    11 	 3.14159258 	 0.00000002
    12 	 3.14159263 	 0.00000001
    13 	 3.14159265 	 0.00000000
    14 	 3.14159265 	 0.00000000
    15 	 3.14159261 	 0.00000001

 Volver a la Tabla de Contenido

### Qué es el FEM?

El [Método de los Elementos Finitos](https://en.wikipedia.org/wiki/Finite_element_method) (FEM, en inglés) es una técnica numérica empleada principalmente para resolver [ecuaciones diferenciales](https://en.wikipedia.org/wiki/Differential_equation), que surgen en el [modelamiento matemático](https://en.wikipedia.org/wiki/Mathematical_model) en áreas como la ingeniería, ciencias básicas, sociales, entre muchas otras.

#### FEM o FEA?

El método de elementos finitos (FEM) es una técnica numérica utilizada para realizar análisis de elementos finitos (FEA) de cualquier fenómeno físico dado.

Es necesario utilizar las matemáticas para comprender y cuantificar de manera integral cualquier fenómeno físico, como el comportamiento estructural o de los fluidos, el transporte térmico, la propagación de ondas y el crecimiento de células biológicas. La mayoría de estos procesos se describen mediante ecuaciones diferenciales parciales (PDE). Sin embargo, para que una computadora resuelva estas PDE, se han desarrollado técnicas numéricas durante las últimas décadas y una de las más destacadas en la actualidad es el método de elementos finitos.

#### Aplicaciones del FEM

El método de elementos finitos comenzó con una promesa significativa en el modelado de varias aplicaciones mecánicas relacionadas con la ingeniería aeroespacial y civil. Las aplicaciones del método de los elementos finitos recién ahora están comenzando a alcanzar su potencial. Una de las perspectivas más interesantes es su aplicación en [problemas acoplados](https://en.wikipedia.org/wiki/Coupling_(physics)) tales como [interacción fluido-estructura](https://en.wikipedia.org/wiki/Fluid%E2%80%93structure_interaction)(FSI), problemas [termomecánicos](https://en.wikipedia.org/wiki/Thermomechanical_analysis), [termoquímicos](https://en.wikipedia.org/wiki/Thermochemical_cycle), [biomecánica](https://en.wikipedia.org/wiki/Biomechanics), [ingeniería biomédica](https://en.wikipedia.org/wiki/Biomedical_sciences), [piezoeléctrica](https://en.wikipedia.org/wiki/Piezoelectricity), y [electromagnética](https://en.wikipedia.org/wiki/Computational_electromagnetics), entre muchos otros.

#### Ecuaciones Diferenciales en Derivadas Parciales

En matemáticas, una [Ecuación Diferencial en Derivadas Parciales](https://en.wikipedia.org/wiki/Partial_differential_equation) (PDE, de sus siglas en inglés) es una ecuación que impone relaciones entre las diversas derivadas parciales de una función multivariable. Existen diferentes formas de escribir una PDE, pero de las más comunes son:

$$u_x=\frac{\partial u}{\partial x}, \quad u_{xx}=\frac{\partial^2 u}{\partial x^2}, \quad u_{xy}=\frac{\partial^2 u}{\partial x \partial y}=\frac{\partial}{\partial y} \left( \frac{\partial u}{\partial x}\right),$$

Es importante comprender los diferentes géneros de PDE y su idoneidad para su uso con FEM. Comprender esto es particularmente importante para todos, independientemente de la motivación para usar el análisis de elementos finitos. Es fundamental recordar que FEM es una herramienta y cualquier herramienta es tan buena como su usuario.

Aunque, como se ha indicado, el FEM es una herramienta para resolver PDEs, en el curso no se realizará un desarrollo teórico de este tema, por lo que se deja al usuario la responsabilidad de su estudio y adecuado entendimiento. En el libro de [Kreyszig](https://soaneemrana.org/onewebmedia/ADVANCED%20ENGINEERING%20MATHEMATICS%20BY%20ERWIN%20ERESZIG1.pdf), capítulo 12, encontrará información que puede ser una buena referencia para estudio. También el notebook de [Ecuaciones Diferenciales con Python](https://relopezbriega.github.io/blog/2016/01/10/ecuaciones-diferenciales-con-python/) del profesor Raúl Lopez Briega, en español, en donde podrá realizar algunas aplicaciones empleando programación en `python`.

#### Cómo trabaja el FEM?

El [principio de minimización de la energía](https://en.wikipedia.org/wiki/Principle_of_minimum_energy) forma la columna vertebral principal del método de elementos finitos. En otras palabras, cuando se aplica una condición de contorno específica a un cuerpo (como desplazamiento o fuerza), esto puede conducir a varias configuraciones, pero sin embargo, solo una configuración particular es posible o lograda de manera realista. Sólo se elige aquella configuración donde la energía total es mínima. Incluso cuando la simulación se realiza varias veces, prevalecen los mismos resultados.

#### Historia del FEM

Dependiendo de la perspectiva histórica, se puede decir que el FEM tuvo sus orígenes en la obra de Euler, ya en el siglo XVI. Sin embargo, los primeros trabajos matemáticos sobre FEM se pueden encontrar en los trabajos de Schellback(1851) y Courant(1943).

El FEM fue desarrollado de forma independiente por ingenieros para abordar problemas de mecánica estructural relacionados con la ingeniería aeroespacial y civil. Los desarrollos comenzaron a mediados de la década de 1950 con los artículos de [Turner, Clough, Martin y Topp](http://www.ce.memphis.edu/7117/notes/presentations/papers/Turner%20et%20al%20(1956)%20Stiffness%20and%20deflection%20analysis%20of%20comlex%20strucutres.pdf)(1956), [Argyris](https://en.wikipedia.org/wiki/John_Argyris)(1957) y [Babuska y Aziz](https://www.elsevier.com/books/the-mathematical-foundations-of-the-finite-element-method-with-applications-to-partial-differential-equations/aziz/978-0-12-068650-6)(1972). Los libros de [Zienkiewicz](https://en.wikipedia.org/wiki/Olgierd_Zienkiewicz)(1971) sentaron las bases para el desarrollo futuro en FEM.

Una revisión interesante de estos desarrollos históricos se puede encontrar en Oden(1991). Se puede encontrar una revisión del desarrollo de FEM durante los últimos 75 años en este artículo de blog: [75 Years of the Finite Element Method](https://www.simscale.com/blog/2015/11/75-years-of-the-finite-element-method-fem/). En el artículo de [Gupta y Meek](https://people.sc.fsu.edu/~jpeterson/history_fem.pdf)(1996) o en este documento de curso del profesor [Mohite](http://home.iitk.ac.in/~mohite/History_of_FEM.pdf), podrá encontrar más información.

 Volver a la Tabla de Contenido

### Diferentes tipos del FEM

Como se discutió anteriormente, la tecnología FEM tradicional ha demostrado deficiencias en los problemas de modelado relacionados con la mecánica de fluidos y la propagación de ondas. Recientemente se han realizado varias mejoras para optimizar el proceso de solución y ampliar la aplicabilidad del análisis de elementos finitos a una amplia gama de problemas. Algunos de los importantes que aún se utilizan incluyen:

#### Método de elementos finitos extendido (XFEM)

El método de Bubnov-Galerkin requiere continuidad de desplazamiento entre elementos. Aunque problemas como el contacto, la fractura y el daño implican discontinuidades y saltos que no pueden manejarse directamente con el método de elementos finitos. Para superar esta deficiencia, [XFEM](https://en.wikipedia.org/wiki/Extended_finite_element_method) nació en la década de 1990. XFEM funciona mediante la expansión de las funciones de forma con las funciones de pasos de Heaviside. Se asignan grados de libertad adicionales a los nodos alrededor del punto de discontinuidad para que se puedan considerar los saltos.

#### Método de elementos finitos generalizado (GFEM)

[GFEM](https://www.sciencedirect.com/science/article/abs/pii/S0045782501001888) se introdujo casi al mismo tiempo que XFEM en los años 90. Combina las características de los métodos tradicionales FEM y sin malla. Las funciones de forma se definen principalmente por las coordenadas globales y luego se multiplican por partición de unidad para crear funciones de forma elementales locales. Una de las ventajas de GFEM es la prevención de remallado alrededor de singularidades.

#### Método mixto de elementos finitos

El [Método mixto de elementos finitos](https://en.wikipedia.org/wiki/Mixed_finite_element_method) se aplica en varios problemas, como el contacto o la incompresibilidad, en el que las restricciones se imponen utilizando multiplicadores de Lagrange. Estos grados de libertad extra que surgen de los multiplicadores de Lagrange se resuelven de forma independiente. El sistema de ecuaciones se resuelve como un sistema acoplado de ecuaciones.

#### Método de elementos finitos hp

[hp-FEM](https://en.wikipedia.org/wiki/Hp-FEM) es una combinación de refinamiento automático de malla (refinamiento h) y un aumento en el orden de polinomio (refinamiento p). Esto no es lo mismo que hacer refinamientos h y p por separado. Cuando se utiliza el refinamiento hp automático y un elemento se divide en elementos más pequeños (refinamiento h), cada elemento también puede tener diferentes órdenes polinomiales.

#### Método de elementos finitos de Galerkin discontinuo (DG-FEM)

[DG-FEM](https://en.wikipedia.org/wiki/Discontinuous_Galerkin_method) ha mostrado una promesa significativa para utilizar la idea de elementos finitos para resolver ecuaciones hiperbólicas, donde los métodos tradicionales de elementos finitos han sido débiles. Además, también ha mostrado mejoras en la flexión y problemas incompresibles que se observan típicamente en la mayoría de los procesos de materiales. Aquí, se agregan restricciones adicionales a la forma débil que incluye un parámetro de penalización (para evitar la interpenetración) y términos para otro equilibrio de tensiones entre los elementos.

 Volver a la Tabla de Contenido

### El proceso de análisis *FEM*

El comportamiento de un fenómeno en un sistema depende de la geometría o dominio del sistema, la propiedad del material o medio y las condiciones de frontera, iniciales y de carga. Para un sistema de ingeniería, la geometría o el dominio pueden ser muy complejos. Además, la frontera y las condiciones iniciales también pueden ser complicadas. Por lo tanto, en general, es muy difícil resolver la ecuación diferencial gobernante por medios analíticos. En la práctica, la mayoría de los problemas se resuelven mediante métodos numéricos. Entre estos, los métodos de discretización de dominios propugnados por el FEM son los más populares, debido a su practicidad y versatilidad.

#### Modelado

 Fuente: Felippa, C. Introduction to Finite Element Methods.

El procedimiento de modelado computacional que utiliza el FEM consta en general de cuatro pasos:

- Modelado de la geometría.

- [Mallado](https://en.wikipedia.org/wiki/Mesh_generation) (discretización).

- Especificación de propiedad material.

- Especificación de condiciones de contorno, inicial y de carga.

***Ejemplo:*** Elementos unidimensionales

 Fuente: Felippa, C. Introduction to Finite Element Methods.

***Ejemplo:*** Elementos bidimensionales

 Fuente: Springer.

#### Simulación

Las etapas del proceso de simulación empleando la técnica FEM se puede resumir en la siguiente tabla:

 Fuente: Miklos Kuczmann

#### Terminología FEM

- ***Dominio:*** es el conjunto de variables independientes para las que se define una función. En el *FEA*, un dominio es un sistema (región) continuo sobre el cual gobiernan las leyes de la física. En la ingeniería estructural, un dominio podría ser una viga o el marco de un edificio completo. En la ingeniería mecánica, un dominio podría ser una pieza de una máquina o un campo térmico.

- ***Ecuaciones gobernantes:*** son las ecuaciones derivadas de la física del sistema. Muchos sistemas de ingeniería pueden describirse mediante ecuaciones de gobierno, que determinan las características y comportamientos del sistema.

- ***Condiciones de frontera:*** son valores de la función en el borde del rango de algunas de sus variables. Es necesario conocer algunas de las condiciones de contorno para resolver un problema de ingeniería o para encontrar una función desconocida.

- ***Elemento:*** es una parte del dominio del problema y, por lo general, tiene una forma simple como un triángulo o cuadrilátero en 2D, o un tetraedro o un sólido rectangular en 3D.

- ***Nodo:*** es un punto en el dominio y, a menudo, es el vértice de varios elementos. Un nodo también se llama punto nodal.

- ***Malla:*** los elementos y nodos, juntos, forman una malla (mesh o grid, en inglés), que es la estructura de datos central en FEA.

- ***Generación de malla:*** la mayoría del software FEA genera automáticamente una malla refinada para lograr resultados más precisos. Para análisis de elementos finitos complejos o a gran escala, a menudo es imperativo que las computadoras generen una malla de elementos finitos automáticamente. Hay muchos algoritmos diferentes para la generación automática de mallas. En [este enlace](http://www.argusone.com/MeshGeneration.html) podrá ver algunas muestras de malla generadas automáticamente.

- ***Análisis lineal:*** se basa en los siguientes supuestos: (1) estático; (2) Pequeños desplazamientos; (3) El material es linealmente elástico.

- ***Análisis no lineal:*** considera la no linealidad del material y/o la no linealidad geométrica de un sistema de ingeniería. El análisis geométrico no lineal también se denomina análisis de grandes deformaciones.

- ***Grados de libertad:*** Los [grados de libertad](https://en.wikipedia.org/wiki/Degrees_of_freedom_(mechanics)) (DOF) son las principales incógnitas en las ecuaciones que constituyen un modelo de elementos finitos. La resolución de las ecuaciones determina los valores de los grados de libertad para cada nodo del modelo. Estos valores se denominan "datos primarios" en la documentación. Los datos derivados (tensiones, deformaciones, gradientes, flujos, etc.) se calculan a partir de la solución DOF. Los DOF incluyen desplazamientos (UX, UY, UZ), rotaciones (ROTX, ROTY, ROTZ), temperatura (TEMP), entre otros. Los grados de libertad incluidos en cada tipo de elemento reflejan la física del problema subyacente. Debe elegir elementos que ofrezcan solo los grados de libertad que necesita, ya que los grados de libertad adicionales aumentan el tiempo de cálculo y no proporcionan ningún beneficio. Por ejemplo, el análisis estructural (generalmente) no implica transferencia de calor o voltajes. Por lo tanto, los elementos utilizados para modelar estructuras no deben tener TEMP o VOLT DOF.

  A continuación se presenta una tabla resumen con los DOF más comunes para diferentes tipos de elementos estructurales:

|Elemento| DOFs                               |
|:------:|:----------------------------------:|
|Cercha  |$$UX, UY, UZ$$                      |
|Viga    |$$UX, UY, UZ; RotX, RotY, RotZ$$|
|Ladrillo|$$UX, UY, UZ$$                      |
|Placa   |$$UX, UY, UZ, Rot2DOFs-plano^*$$    |

  $^*$El *DOF* rotacional fuera del plano no se considera para elementos de placa.

 Volver a la Tabla de Contenido

### Pasos en la solución por el FEM

El desarrollo de un análisis por elementos finitos involucra una serie de pasos hasta determinar las fuerzas nodales y desplazamientos ocurridos en la estructura. A seguir se enuncian, de forma muy general, cada uno de ellos. A lo largo del curso se entrará más en detalle.

#### Discretizar la estructura en elementos.

Estos elementos están conectados entre sí a través de nodos.

 Fuente: mechanicalc.com

En este primer paso, se compone un modelo matemático de la estructura. Este modelo es una aproximación de la estructura, mientras que la estructura física es continua, el modelo consta de elementos discretos.

En este ejemplo se utiliza elementos tipo viga que se basan en la [teoría de vigas de Euler-Bernoulli](https://en.wikipedia.org/wiki/Euler%E2%80%93Bernoulli_beam_theory). El elemento tiene $2$ nodos, cada uno de los cuales tiene $3$ grados de libertad: traslación en $x$, traslación en $y$ y rotación.

#### Determinar la matriz de rigidez local para cada elemento

Esta matriz representa la rigidez de cada nodo en el elemento en un grado de libertad específico (es decir, determina el desplazamiento de cada nodo en cada grado de libertad bajo una carga dada). Debido a que cada uno de los nodos en el elemento de viga tiene $3$ grados de libertad, una matriz de $6 \times 6$ puede describir completamente la rigidez del elemento.

\begin{equation}
k^{(e)}=
\begin{bmatrix}
\frac{AE}{L} & 0 & 0 & -\frac{AE}{L} & 0 & 0 \\
0 & \frac{12EI}{L^3} & \frac{6EI}{L^2} & 0 & -\frac{12EI}{L^3} & \frac{6EI}{L^2}\\
0 & \frac{6EI}{L^2} & \frac{4EI}{L} & 0 & -\frac{6EI}{L^2} & \frac{2EI}{L}\\
-\frac{AE}{L} & 0 & 0 & \frac{AE}{L} & 0 & 0 \\
0 & -\frac{12EI}{L^3} & -\frac{6EI}{L^2} & 0 & \frac{12EI}{L^3} & -\frac{6EI}{L^2}\\
0 & \frac{6EI}{L^2} & \frac{2EI}{L} & 0 & -\frac{6EI}{L^2} & \frac{4EI}{L}
\end{bmatrix}
\end{equation}

#### Ensamblar la matriz de rigidez global

La matriz de rigidez global para la estructura general se ensambla basándose en la combinación de las matrices de rigidez local. En un nivel alto, la matriz de rigidez global se crea sumando las matrices de rigidez locales:

$$\left[ K\right]=\sum_i \left[ k_i^{(e)}\right]$$

donde la matriz $[k^{(e)}_i]$ es la matriz de rigidez local del elemento $i$.

La matriz de rigidez global será una matriz cuadrada $n \times n$, donde $n$ es $3$ veces el número de nodos en la malla (ya que cada nodo tiene $3$ grados de libertad). Al ensamblar la matriz de rigidez global, los términos de rigidez para cada nodo en la matriz de rigidez elemental se colocan en la ubicación correspondiente en la matriz global. Para cualquier elemento que comparta un nodo, las contribuciones de rigidez de ese nodo se sumarán de cada elemento.

#### Cálcular el vector de fuerza aplicada

El vector de fuerza aplicada será un vector $n \times 1$, donde $n$ es $3$ veces el número de nodos en la malla. El vector de fuerza se ensambla incluyendo las fuerzas aplicadas en cada grado de libertad en cada nodo de la malla:

$$\left\{ F\right\} =\sum_i \left\{ f_i^{(e)} \right\}$$

Una vez que se construyen la matriz de rigidez global y el vector de fuerza aplicada, se pueden resolver los desplazamientos nodales. La siguiente ecuación relaciona las fuerzas y los desplazamientos en la estructura general:

$$\left\{F\right\}+\left\{R\right\}=\left[K\right]\left\{U\right\}$$

donde

- $\left\{F\right\}$ es el vector de fuerza aplicada,

- $\left\{R\right\}$ es el vector de reacción externa,

- $\left[K\right]$ es la matriz de rigidez global, y

- $\left\{U\right\}$ es el vector de desplazamiento nodal.

En la ecuación anterior, $\left\{R\right\}$ y $\left\{U\right\}$ son incógnitas. Para simplificar la solución de esta ecuación, se desea resolver las reacciones y los desplazamientos independientemente unos de otros. Para hacer esto, podemos usar el hecho de que para cada restricción que se aplicó (ya sea una restricción en traslación en $x$, traslación en $y$, o rotación), el desplazamiento asociado con esa restricción será cero. Además, las reacciones externas solo ocurrirán donde se aplicaron restricciones. Por lo tanto, se sabe que para cada grado de libertad en cada nodo:

- Si se aplica una restricción, el desplazamiento en esa restricción será cero y la reacción externa puede ser distinta de cero.

- Si no hay restricción, la reacción externa será cero y el desplazamiento puede ser distinto de cero.

#### Aplicar condiciones de contorno y resolver los desplazamientos nodales

Inicialmente resolveremos la ecuación anterior para los desplazamientos nodales, $\left\{U\right\}$. Con base en el razonamiento anterior, es posible eliminar el vector $\left\{R\right\}$ de la ecuación. Las condiciones de contorno se aplican a la ecuación "poniendo a cero" las filas en las matrices correspondientes a las restricciones aplicadas. Al hacer esto, no hemos perdido ninguna información sobre los desplazamientos nodales, ya que se sabe que los desplazamientos serán cero para cada fila que se puso a cero. Ahora la ecuación se ha reducido a:

$$\left\{F\right\}=\left[K\right]\left\{U\right\}$$

#### Resolver las reacciones externas

La ecuación anterior se puede resolver para $\left\{U\right\}$, después de lo cual se conocen todos los desplazamientos nodales. La única incógnita que queda en la ecuación original es $\left\{R\right\}$, y esto ahora se puede resolver para:

$$\left\{R\right\}=\left[K\right]\left\{U\right\}-\left\{F\right\}$$

Ahora es posible resolver las fuerzas en cada nodo.

#### Resolver las fuerzas nodales

Para cualquier fuerza de reacción encontrada, la fuerza nodal es igual a la fuerza de reacción. Sin embargo, para los nodos donde no hay una reacción externa, las fuerzas en el nodo aún se desconocen. Estos se pueden encontrar extrayendo los desplazamientos apropiados del vector $\left\{U\right\}$ global para construir un vector $\left\{u\right\}$ local para cada elemento. Cada uno de estos vectores locales será un $6 \times 1$. Las fuerzas nodales en cada elemento se pueden resolver usando:

$$\left\{f\right\} = \left[k\right] \left\{u\right\}$$

donde

- $\left\{f\right\}$ es el vector de fuerza del elemento (local),

- $\left[k\right]$ es la matriz de rigidez del elemento, y

- $\left\{u\right\}$ es el vector de desplazamiento del elemento.

#### Resolver las tensiones

Una vez que se conocen las fuerzas en cada nodo de la malla, es posible resolver los esfuerzo en cada nodo:

| Esfuerzo axial |Esfuerzo cortante|Esfuerzo a flexión|Esfuerzos de von Mises|
|:--------------:|:---------------:|:----------------:|:--------------------:|
|$$\sigma_{ax}=\frac{F_{ax}}{A}$$ |$$\tau_{sh}=\frac{F_{sh}}{A}$$|$$\sigma_b=\frac{Mc}{I}$$|$$\sigma_{vm}=\sqrt{(\sigma_{ax}+\sigma_b)^2+3\tau_{sh}^2}$$|

 Volver a la Tabla de Contenido

### Atributos de elementos

Existe una ámplia tecnología de elementos en el FEM. Entre los tipos de elementos más comunes se tienen:

en la siguiente imágen se observa la comparación entre un componente estructural físico y su equivalente a un elemento finito idealizado

 Fuente: Felippa, C. Introduction to Finite Element Methods.

 Volver a la Tabla de Contenido

### Aspectos computacionales del FEM

Coming soon...

 Volver a la Tabla de Contenido

```python
from IPython.core.display import HTML
def css_styling():
    styles = open('./nb_style.css', 'r').read()
    return HTML(styles)
css_styling()
```

<link href='http://fonts.googleapis.com/css?family=Fenix' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:300,400' rel='stylesheet' type='text/css'>

```python

```


```python
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

np.random.seed(1789)

from IPython.core.display import HTML
def css_styling():
    styles = open("styles/custom.css", "r").read()
    return HTML(styles)
css_styling()
```

# Statistical Data Modeling

Pandas, NumPy and SciPy provide the core functionality for building statistical models of our data. We use models to:

- Concisely **describe** the components of our data
- Provide **inference** about underlying parameters that may have generated the data
- Make **predictions** about unobserved data, or expected future observations.

This section of the tutorial illustrates how to use Python to build statistical models of low to moderate difficulty from scratch, and use them to extract estimates and associated measures of uncertainty.

Estimation
==========

An recurring statistical problem is finding estimates of the relevant parameters that correspond to the distribution that best represents our data.

In **parametric** inference, we specify *a priori* a suitable distribution, then choose the parameters that best fit the data.

* e.g. the mean $\mu$ and the variance $\sigma^2$ in the case of the normal distribution

```python
x = np.array([ 1.00201077,  1.58251956,  0.94515919,  6.48778002,  1.47764604,
        5.18847071,  4.21988095,  2.85971522,  3.40044437,  3.74907745,
        1.18065796,  3.74748775,  3.27328568,  3.19374927,  8.0726155 ,
        0.90326139,  2.34460034,  2.14199217,  3.27446744,  3.58872357,
        1.20611533,  2.16594393,  5.56610242,  4.66479977,  2.3573932 ])
_ = plt.hist(x, bins=7)
```

### Fitting data to probability distributions

We start with the problem of finding values for the parameters that provide the best fit between the model and the data, called point estimates. First, we need to define what we mean by ‘best fit’. There are two commonly used criteria:

* **Method of moments** chooses the parameters so that the sample moments (typically the sample mean and variance) match the theoretical moments of our chosen distribution.
* **Maximum likelihood** chooses the parameters to maximize the likelihood, which measures how likely it is to observe our given sample.

### Discrete Random Variables

$$X = \{0,1\}$$

$$Y = \{\ldots,-2,-1,0,1,2,\ldots\}$$

**Probability Mass Function**:

For discrete $X$,

$$Pr(X=x) = f(x|\theta)$$

***e.g. Poisson distribution***

The Poisson distribution models unbounded counts:

$$Pr(X=x)=\frac{e^{-\lambda}\lambda^x}{x!}$$

* $X=\{0,1,2,\ldots\}$
* $\lambda > 0$

$$E(X) = \text{Var}(X) = \lambda$$

### Continuous Random Variables

$$X \in [0,1]$$

$$Y \in (-\infty, \infty)$$

**Probability Density Function**:

For continuous $X$,

$$Pr(x \le X \le x + dx) = f(x|\theta)dx \, \text{ as } \, dx \rightarrow 0$$

***e.g. normal distribution***

$$f(x) = \frac{1}{\sqrt{2\pi\sigma^2}}\exp\left[-\frac{(x-\mu)^2}{2\sigma^2}\right]$$

* $X \in \mathbf{R}$
* $\mu \in \mathbf{R}$
* $\sigma>0$

$$\begin{align}E(X) &= \mu \cr
\text{Var}(X) &= \sigma^2 \end{align}$$

### Example: Nashville Precipitation

The dataset `nashville_precip.txt` contains [NOAA precipitation data for Nashville measured since 1871](http://bit.ly/nasvhville_precip_data).

The gamma distribution is often a good fit to aggregated rainfall data, and will be our candidate distribution in this case.

```python
precip = pd.read_table("../data/nashville_precip.txt", index_col=0, na_values='NA', delim_whitespace=True)
precip.head()
```

      Jan
      Feb
      Mar
      Apr
      May
      Jun
      Jul
      Aug
      Sep
      Oct
      Nov
      Dec

      Year

      1871
      2.76
      4.58
      5.01
      4.13
      3.30
      2.98
      1.58
      2.36
      0.95
      1.31
      2.13
      1.65

      1872
      2.32
      2.11
      3.14
      5.91
      3.09
      5.17
      6.10
      1.65
      4.50
      1.58
      2.25
      2.38

      1873
      2.96
      7.14
      4.11
      3.59
      6.31
      4.20
      4.63
      2.36
      1.81
      4.28
      4.36
      5.94

      1874
      5.22
      9.23
      5.36
      11.84
      1.49
      2.87
      2.65
      3.52
      3.12
      2.63
      6.12
      4.19

      1875
      6.15
      3.06
      8.14
      4.22
      1.73
      5.63
      8.12
      1.60
      3.79
      1.25
      5.46
      4.30

```python
_ = precip.hist(sharex=True, sharey=True, grid=False)
plt.tight_layout()
```

The first step is recognizing what sort of distribution to fit our data to. A couple of observations:

1. The data are skewed, with a longer tail to the right than to the left
2. The data are positive-valued, since they are measuring rainfall
3. The data are continuous

There are a few possible choices, but one suitable alternative is the **gamma distribution**:

$$x \sim \text{Gamma}(\alpha, \beta) = \frac{\beta^{\alpha}x^{\alpha-1}e^{-\beta x}}{\Gamma(\alpha)}$$

The ***method of moments*** simply assigns the empirical mean and variance to their theoretical counterparts, so that we can solve for the parameters.

So, for the gamma distribution, the mean and variance are:

$$ \hat{\mu} = \bar{X} = \alpha \beta $$
$$ \hat{\sigma}^2 = S^2 = \alpha \beta^2 $$

So, if we solve for these parameters, we can use a gamma distribution to describe our data:

$$ \alpha = \frac{\bar{X}^2}{S^2}, \, \beta = \frac{S^2}{\bar{X}} $$

Let's deal with the missing value in the October data. Given what we are trying to do, it is most sensible to fill in the missing value with the average of the available values. We will learn more sophisticated methods for handling missing data later in the course.

```python
precip.fillna(value={'Oct': precip.Oct.mean()}, inplace=True)
```

      Jan
      Feb
      Mar
      Apr
      May
      Jun
      Jul
      Aug
      Sep
      Oct
      Nov
      Dec

      Year

      1871
      2.76
      4.58
      5.01
      4.13
      3.30
      2.98
      1.58
      2.36
      0.95
      1.31
      2.13
      1.65

      1872
      2.32
      2.11
      3.14
      5.91
      3.09
      5.17
      6.10
      1.65
      4.50
      1.58
      2.25
      2.38

      1873
      2.96
      7.14
      4.11
      3.59
      6.31
      4.20
      4.63
      2.36
      1.81
      4.28
      4.36
      5.94

      1874
      5.22
      9.23
      5.36
      11.84
      1.49
      2.87
      2.65
      3.52
      3.12
      2.63
      6.12
      4.19

      1875
      6.15
      3.06
      8.14
      4.22
      1.73
      5.63
      8.12
      1.60
      3.79
      1.25
      5.46
      4.30

      1876
      6.41
      2.22
      5.28
      3.62
      3.40
      5.65
      7.15
      5.77
      2.52
      2.68
      1.26
      0.95

      1877
      4.05
      1.06
      4.98
      9.47
      1.25
      6.02
      3.25
      4.16
      5.40
      2.61
      4.93
      2.49

      1878
      3.34
      2.10
      3.48
      6.88
      2.33
      3.28
      9.43
      5.02
      1.28
      2.17
      3.20
      6.04

      1879
      6.32
      3.13
      3.81
      2.88
      2.88
      2.50
      8.47
      4.62
      5.18
      2.90
      5.85
      9.15

      1880
      3.74
      12.37
      8.16
      5.26
      4.13
      3.97
      5.69
      2.22
      5.39
      7.24
      5.77
      3.32

      1881
      3.54
      5.48
      2.79
      5.12
      3.67
      3.70
      0.86
      1.81
      6.57
      4.80
      4.89
      4.85

      1882
      14.51
      8.61
      9.38
      3.59
      7.38
      2.54
      4.06
      5.54
      1.61
      1.11
      3.60
      1.52

      1883
      3.76
      7.90
      3.98
      9.12
      4.82
      3.82
      4.94
      4.47
      2.23
      5.27
      3.11
      4.97

      1884
      7.20
      8.18
      8.89
      3.51
      3.58
      6.53
      3.18
      2.81
      2.36
      2.43
      1.57
      3.78

      1885
      6.29
      2.00
      2.33
      3.75
      4.36
      3.72
      5.26
      1.02
      5.60
      2.99
      2.73
      2.90

      1886
      5.18
      3.82
      4.76
      2.36
      2.10
      7.69
      1.90
      5.50
      3.68
      0.51
      5.76
      1.48

      1887
      5.13
      8.47
      3.36
      2.67
      3.43
      2.31
      3.77
      2.89
      6.85
      1.92
      2.29
      5.31

      1888
      6.29
      3.78
      6.46
      4.18
      2.97
      4.68
      2.36
      7.03
      3.82
      2.82
      4.33
      1.77

      1889
      3.83
      1.84
      2.47
      2.83
      5.30
      5.33
      2.74
      1.57
      6.81
      1.54
      6.88
      1.17

      1890
      8.10
      10.95
      8.64
      3.84
      4.16
      2.23
      0.46
      6.59
      5.86
      3.01
      2.01
      4.12

      1891
      6.15
      6.96
      10.31
      2.24
      2.39
      6.50
      1.49
      3.72
      1.25
      0.84
      6.71
      4.26

      1892
      2.81
      2.73
      4.10
      7.45
      4.03
      5.01
      5.13
      3.39
      4.78
      0.25
      3.91
      6.43

      1893
      1.27
      4.88
      3.37
      4.11
      7.31
      4.74
      2.12
      1.92
      6.43
      3.68
      2.97
      3.50

      1894
      4.28
      8.65
      2.69
      4.05
      2.53
      3.55
      5.45
      2.43
      3.07
      0.53
      1.92
      2.81

      1895
      5.71
      0.98
      5.09
      3.07
      2.05
      2.90
      7.14
      1.40
      6.69
      1.57
      2.14
      4.09

      1896
      1.37
      3.65
      6.45
      2.92
      4.05
      1.82
      7.33
      1.40
      2.74
      0.98
      5.71
      1.79

      1897
      3.13
      3.84
      8.49
      5.79
      1.22
      1.82
      8.53
      2.34
      0.19
      0.92
      2.83
      4.93

      1898
      9.46
      0.63
      5.36
      3.16
      1.80
      4.97
      4.50
      6.56
      4.87
      3.21
      3.09
      2.41

      1899
      5.59
      5.19
      7.81
      3.25
      3.36
      0.75
      6.44
      2.53
      1.50
      1.83
      1.55
      4.64

      1900
      2.61
      3.80
      2.20
      4.04
      1.86
      10.35
      2.87
      1.24
      4.55
      3.93
      8.87
      2.22

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      1982
      6.50
      4.80
      3.00
      4.36
      4.19
      2.28
      5.47
      3.46
      3.23
      1.91
      3.87
      6.36

      1983
      2.56
      2.93
      3.44
      6.80
      11.04
      3.93
      1.71
      1.36
      0.45
      2.77
      6.98
      7.75

      1984
      1.79
      2.38
      5.14
      8.41
      9.68
      4.49
      6.63
      2.42
      0.97
      6.00
      6.20
      2.38

      1985
      3.02
      3.30
      2.70
      2.91
      2.65
      1.53
      2.00
      3.91
      2.52
      1.59
      3.81
      0.98

      1986
      0.19
      3.59
      2.29
      0.52
      3.36
      2.38
      0.77
      3.38
      2.19
      2.19
      7.43
      3.31

      1987
      1.61
      4.87
      1.18
      1.03
      4.41
      2.82
      2.56
      0.73
      1.95
      0.21
      3.40
      5.46

      1988
      3.73
      2.02
      2.18
      2.09
      1.86
      0.45
      3.26
      2.39
      2.45
      1.54
      5.49
      3.95

      1989
      4.52
      9.36
      5.31
      2.68
      4.61
      7.87
      3.18
      3.67
      6.30
      3.62
      3.94
      1.97

      1990
      2.76
      4.73
      3.26
      1.60
      2.80
      2.37
      4.86
      3.12
      2.13
      4.41
      4.29
      10.76

      1991
      2.92
      5.44
      4.25
      3.35
      5.63
      1.25
      2.82
      1.79
      5.47
      3.88
      2.87
      7.27

      1992
      2.97
      2.60
      4.50
      0.77
      3.12
      4.31
      5.89
      3.25
      3.45
      1.62
      4.48
      2.88

      1993
      2.76
      3.33
      5.50
      3.33
      4.50
      5.31
      3.64
      1.76
      2.90
      2.20
      2.53
      6.62

      1994
      4.36
      6.18
      7.56
      5.72
      3.76
      8.08
      4.82
      5.05
      4.20
      3.31
      4.04
      2.69

      1995
      5.61
      1.81
      3.87
      3.95
      7.66
      3.69
      1.95
      3.40
      5.00
      5.60
      3.98
      2.32

      1996
      3.82
      2.46
      5.15
      3.68
      4.48
      3.68
      5.45
      1.09
      4.88
      3.16
      6.00
      4.77

      1997
      4.19
      3.10
      9.64
      2.42
      4.92
      6.66
      3.26
      3.52
      5.75
      2.71
      6.59
      2.19

      1998
      3.68
      4.11
      3.13
      6.31
      4.46
      11.95
      4.63
      2.93
      1.39
      1.59
      1.30
      6.53

      1999
      9.28
      2.33
      4.27
      2.29
      4.35
      3.56
      3.19
      3.05
      1.97
      2.04
      2.99
      2.50

      2000
      3.52
      3.75
      3.34
      6.23
      7.66
      1.74
      2.25
      1.95
      1.90
      0.26
      6.39
      3.44

      2001
      3.21
      8.54
      2.73
      2.42
      5.54
      4.47
      2.77
      4.07
      1.79
      4.61
      5.09
      3.32

      2002
      4.93
      1.99
      9.40
      4.31
      3.98
      3.76
      5.64
      3.13
      6.29
      4.48
      2.91
      5.81

      2003
      1.59
      8.47
      2.30
      4.69
      10.73
      7.08
      2.87
      3.88
      8.70
      1.80
      4.17
      3.19

      2004
      3.60
      5.77
      4.81
      6.69
      6.90
      3.39
      3.19
      4.24
      4.55
      4.90
      5.21
      5.93

      2005
      4.42
      3.84
      3.90
      6.93
      1.03
      2.70
      2.39
      6.89
      1.44
      0.02
      3.29
      2.46

      2006
      6.57
      2.69
      2.90
      4.14
      4.95
      2.19
      2.64
      5.20
      4.00
      2.98
      4.05
      3.41

      2007
      3.32
      1.84
      2.26
      2.75
      3.30
      2.37
      1.47
      1.38
      1.99
      4.95
      6.20
      3.83

      2008
      4.76
      2.53
      5.56
      7.20
      5.54
      2.21
      4.32
      1.67
      0.88
      5.03
      1.75
      6.72

      2009
      4.59
      2.85
      2.92
      4.13
      8.45
      4.53
      6.03
      2.14
      11.08
      6.49
      0.67
      3.99

      2010
      4.13
      2.77
      3.52
      3.48
      16.43
      4.96
      5.86
      6.99
      1.17
      2.49
      5.41
      1.87

      2011
      2.31
      5.54
      4.59
      7.51
      4.38
      5.04
      3.46
      1.78
      6.20
      0.93
      6.15
      4.25

141 rows × 12 columns

Now, let's calculate the sample moments of interest, the means and variances by month:

```python
precip_mean = precip.mean()
precip_mean
```

    Jan    4.523688
    Feb    4.097801
    Mar    4.977589
    Apr    4.204468
    May    4.325674
    Jun    3.873475
    Jul    3.895461
    Aug    3.367305
    Sep    3.377660
    Oct    2.610500
    Nov    3.685887
    Dec    4.176241
    dtype: float64

```python
precip_var = precip.var()
precip_var
```

    Jan    6.928862
    Feb    5.516660
    Mar    5.365444
    Apr    4.117096
    May    5.306409
    Jun    5.033206
    Jul    3.777012
    Aug    3.779876
    Sep    4.940099
    Oct    2.741659
    Nov    3.679274
    Dec    5.418022
    dtype: float64

We then use these moments to estimate $\alpha$ and $\beta$ for each month:

```python
alpha_mom = precip_mean ** 2 / precip_var
beta_mom = precip_var / precip_mean
```

```python
alpha_mom, beta_mom
```

    (Jan    2.953407
     Feb    3.043866
     Mar    4.617770
     Apr    4.293694
     May    3.526199
     Jun    2.980965
     Jul    4.017624
     Aug    2.999766
     Sep    2.309383
     Oct    2.485616
     Nov    3.692511
     Dec    3.219070
     dtype: float64, Jan    1.531684
     Feb    1.346249
     Mar    1.077920
     Apr    0.979219
     May    1.226724
     Jun    1.299403
     Jul    0.969593
     Aug    1.122522
     Sep    1.462581
     Oct    1.050243
     Nov    0.998206
     Dec    1.297344
     dtype: float64)

We can use the `gamma.pdf` function in `scipy.stats.distributions` to plot the ditribtuions implied by the calculated alphas and betas. For example, here is January:

```python
from scipy.stats.distributions import gamma

precip.Jan.hist(normed=True, bins=20)
plt.plot(np.linspace(0, 10), gamma.pdf(np.linspace(0, 10), alpha_mom[0], beta_mom[0]))
```

Looping over all months, we can create a grid of plots for the distribution of rainfall, using the gamma distribution:

```python
axs = precip.hist(normed=True, figsize=(12, 8), sharex=True, sharey=True, bins=15, grid=False)

for ax in axs.ravel():

    # Get month
    m = ax.get_title()

    # Plot fitted distribution
    x = np.linspace(*ax.get_xlim())
    ax.plot(x, gamma.pdf(x, alpha_mom[m], beta_mom[m]))

    # Annotate with parameter estimates
    label = 'alpha = {0:.2f}\nbeta = {1:.2f}'.format(alpha_mom[m], beta_mom[m])
    ax.annotate(label, xy=(10, 0.2))

plt.tight_layout()
```

Maximum Likelihood
==================

**Maximum likelihood** (ML) fitting is usually more work than the method of moments, but it is preferred as the resulting estimator is known to have good theoretical properties.

There is a ton of theory regarding ML. We will restrict ourselves to the mechanics here.

Say we have some data $y = y_1,y_2,\ldots,y_n$ that is distributed according to some distribution:

$$Pr(Y_i=y_i | \theta)$$

Here, for example, is a **Poisson distribution** that describes the distribution of some discrete variables, typically *counts*:

```python
y = np.random.poisson(5, size=100)
plt.hist(y, bins=12, normed=True)
plt.xlabel('y'); plt.ylabel('Pr(y)')
```

The product $\prod_{i=1}^n Pr(y_i | \theta)$ gives us a measure of how **likely** it is to observe values $y_1,\ldots,y_n$ given the parameters $\theta$.

Maximum likelihood fitting consists of choosing the appropriate function $l= Pr(Y|\theta)$ to maximize for a given set of observations. We call this function the *likelihood function*, because it is a measure of how likely the observations are if the model is true.

> Given these data, how likely is this model?

In the above model, the data were drawn from a Poisson distribution with parameter $\lambda =5$.

$$L(y|\lambda=5) = \frac{e^{-5} 5^y}{y!}$$

So, for any given value of $y$, we can calculate its likelihood:

```python
poisson_like = lambda x, lam: np.exp(-lam) * (lam**x) / (np.arange(x)+1).prod()

lam = 6
value = 10
poisson_like(value, lam)
```

    0.041303093412337726

```python
np.sum(poisson_like(yi, lam) for yi in y)
```

    11.499056250911673

```python
lam = 8
np.sum(poisson_like(yi, lam) for yi in y)
```

    7.8028592304816868

We can plot the likelihood function for any value of the parameter(s):

```python
lambdas = np.linspace(0,15)
x = 5
plt.plot(lambdas, [poisson_like(x, l) for l in lambdas])
plt.xlabel('$\lambda$')
plt.ylabel('L($\lambda$|x={0})'.format(x))
```

How is the likelihood function different than the probability distribution function (PDF)? The likelihood is a function of the parameter(s) *given the data*, whereas the PDF returns the probability of data given a particular parameter value. Here is the PDF of the Poisson for $\lambda=5$.

```python
lam = 5
xvals = np.arange(15)
plt.bar(xvals, [poisson_like(x, lam) for x in xvals], width=0.2)
plt.xlabel('x')
plt.ylabel('Pr(X|$\lambda$=5)')
```

*Why are we interested in the likelihood function?*

A reasonable estimate of the true, unknown value for the parameter is one which **maximizes the likelihood function**. So, inference is reduced to an optimization problem.

Going back to the rainfall data, if we are using a gamma distribution we need to maximize:

$$\begin{align}l(\alpha,\beta) &= \sum_{i=1}^n \log[\beta^{\alpha} x^{\alpha-1} e^{-x/\beta}\Gamma(\alpha)^{-1}] \cr
&= n[(\alpha-1)\overline{\log(x)} - \bar{x}\beta + \alpha\log(\beta) - \log\Gamma(\alpha)]\end{align}$$

*N.B.: Its usually easier to work in the log scale*

where $n = 2012 − 1871 = 141$ and the bar indicates an average over all *i*. We choose $\alpha$ and $\beta$ to maximize $l(\alpha,\beta)$.

Notice $l$ is infinite if any $x$ is zero. We do not have any zeros, but we do have an NA value for one of the October data, which we dealt with above.

### Finding the MLE

To find the maximum of any function, we typically take the *derivative* with respect to the variable to be maximized, set it to zero and solve for that variable.

$$\frac{\partial l(\alpha,\beta)}{\partial \beta} = n\left(\frac{\alpha}{\beta} - \bar{x}\right) = 0$$

Which can be solved as $\beta = \alpha/\bar{x}$. However, plugging this into the derivative with respect to $\alpha$ yields:

$$\frac{\partial l(\alpha,\beta)}{\partial \alpha} = \log(\alpha) + \overline{\log(x)} - \log(\bar{x}) - \frac{\Gamma(\alpha)'}{\Gamma(\alpha)} = 0$$

This has no closed form solution. We must use ***numerical optimization***!

Numerical optimization alogarithms take an initial "guess" at the solution, and **iteratively** improve the guess until it gets "close enough" to the answer.

Here, we will use *Newton-Raphson* method, which is a **root-finding algorithm**:

$$x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}$$

which is available to us via SciPy:

```python
from scipy.optimize import newton
```

Here is a graphical example of how Newton-Raphson converges on a solution, using an arbitrary function:

```python
%run newton_raphson_plot.py
```

To apply the Newton-Raphson algorithm, we need a function that returns a vector containing the **first and second derivatives** of the function with respect to the variable of interest. The second derivative of the gamma distribution with respect to $\alpha$ is:

$$\frac{\partial^2 l(\alpha,\beta)}{\partial \alpha^2} = \frac{1}{\alpha} - \frac{\partial}{\partial \alpha} \left[ \frac{\Gamma(\alpha)'}{\Gamma(\alpha)} \right]$$

```python
from scipy.special import psi, polygamma

dlgamma = lambda a, log_mean, mean_log: np.log(a) - psi(a) - log_mean + mean_log
dl2gamma = lambda a, *args: 1./a - polygamma(1, a)
```

where `log_mean` and `mean_log` are $\log{\bar{x}}$ and $\overline{\log(x)}$, respectively. `psi` and `polygamma` are complex functions of the Gamma function that result when you take first and second derivatives of that function.

```python
# Calculate statistics
log_mean = precip.mean().apply(np.log)
mean_log = precip.apply(np.log).mean()
```

Time to optimize!

```python
# Alpha MLE for December
alpha_mle = newton(dlgamma, 2, dl2gamma, args=(log_mean[-1], mean_log[-1]))
alpha_mle
```

    3.5189679152399647

And now plug this back into the solution for beta:

$$ \beta  = \frac{\alpha}{\bar{X}} $$

```python
beta_mle = alpha_mle/precip.mean()[-1]
beta_mle
```

    0.84261607548413797

We can compare the fit of the estimates derived from MLE to those from the method of moments:

```python
dec = precip.Dec
dec.hist(normed=True, bins=10, grid=False)
x = np.linspace(0, dec.max())
plt.plot(x, gamma.pdf(x, alpha_mom[-1], beta_mom[-1]), 'm-', label='Moment estimator')
plt.plot(x, gamma.pdf(x, alpha_mle, beta_mle), 'r--', label='ML estimator')
plt.legend()
```

For some common distributions, SciPy includes methods for fitting via MLE:

```python
from scipy.stats import gamma

gamma.fit(precip.Dec)
```

    (2.2427517753152308, 0.65494604470188622, 1.570073932063466)

This fit is not directly comparable to our estimates, however, because SciPy's `gamma.fit` method fits an odd 3-parameter version of the gamma distribution.

### Model checking

An informal way of checking the fit of our parametric model is to compare the observed quantiles of the data to those of the theoretical model we are fitting it to. If the model is a good fit, the points should fall on a 45-degree reference line. This is called a **probability plot**.

SciPy includes a `probplot` function that generates probability plots based on the data and a specified distribution.

```python
from scipy.stats import probplot

probplot(precip.Dec, dist=gamma(3.51, scale=0.84), plot=plt);
```

### Example: truncated distribution

Suppose that we observe $Y$ truncated below at $a$ (where $a$ is known). If $X$ is the distribution of our observation, then:

$$ P(X \le x) = P(Y \le x|Y \gt a) = \frac{P(a \lt Y \le x)}{P(Y \gt a)}$$

(so, $Y$ is the original variable and $X$ is the truncated variable)

Then X has the density:

$$f_X(x) = \frac{f_Y (x)}{1−F_Y (a)} \, \text{for} \, x \gt a$$

Suppose $Y \sim N(\mu, \sigma^2)$ and $x_1,\ldots,x_n$ are independent observations of $X$. We can use maximum likelihood to find $\mu$ and $\sigma$.

First, we can simulate a truncated distribution using a `while` statement to eliminate samples that are outside the support of the truncated distribution.

```python
x = np.random.normal(size=10000)

# Truncation point
a = -1

# Resample until all points meet criterion
x_small = x < a
while x_small.sum():
    x[x_small] = np.random.normal(size=x_small.sum())
    x_small = x < a

_ = plt.hist(x, bins=100)
```

We can construct a log likelihood for this function using the conditional form:

$$f_X(x) = \frac{f_Y (x)}{1−F_Y (a)} \, \text{for} \, x \gt a$$

The denominator normalizes the truncated distribution so that it integrates to one.

```python
from scipy.stats.distributions import norm

trunc_norm = lambda theta, a, x: -(np.log(norm.pdf(x, theta[0], theta[1])) -
                                      np.log(1 - norm.cdf(a, theta[0], theta[1]))).sum()
```

For this example, we will use an optimization algorithm, the **Nelder-Mead simplex algorithm**. It has a couple of advantages:

- it does not require derivatives
- it can optimize (minimize) a vector of parameters

SciPy implements this algorithm in its `fmin` function:

```python
from scipy.optimize import fmin

fmin(trunc_norm, np.array([1,2]), args=(-1, x))
```

    Optimization terminated successfully.
             Current function value: 11084.885149
             Iterations: 47
             Function evaluations: 88

    array([ 0.00531007,  1.00652051])

In general, simulating data is a terrific way of testing your model before using it with real data.

## Kernel density estimates

In some instances, we may not be interested in the parameters of a particular distribution of data, but just a smoothed representation of the data at hand. In this case, we can estimate the disribution *non-parametrically* (i.e. making no assumptions about the form of the underlying distribution) using kernel density estimation.

```python
# Some random data
y = np.random.normal(10, size=15)
y
```

    array([  9.68444175,   6.86641455,   8.90236824,   8.48662651,
             9.4599326 ,   9.83248454,   9.45367613,  12.95585035,
            10.58747989,  10.94829904,  10.48694903,   9.13200438,
             9.73979452,  10.14852737,   8.62582257])

The kernel estimator is a sum of symmetric densities centered at each observation. The selected kernel function determines the shape of each component while the **bandwidth** determines their spread. For example, if we use a Gaussian kernel function, the variance acts as the bandwidth.

```python
x = np.linspace(7, 13, 100)
# Smoothing parameter
s = 0.3
# Calculate the kernels
kernels = np.transpose([norm.pdf(x, yi, s) for yi in y])
plt.plot(x, kernels, 'k:')
plt.plot(x, kernels.sum(1))
plt.plot(y, np.zeros(len(y)), 'ro', ms=10)
```

SciPy implements a Gaussian KDE that automatically chooses an appropriate bandwidth. Let's create a bi-modal distribution of data that is not easily summarized by a parametric distribution:

```python
# Create a bi-modal distribution with a mixture of Normals.
x1 = np.random.normal(0, 2, 50)
x2 = np.random.normal(5, 1, 50)

# Append by row
x = np.r_[x1, x2]
```

```python
plt.hist(x, bins=10, normed=True)
```

```python
from scipy.stats import kde

density = kde.gaussian_kde(x)
xgrid = np.linspace(x.min(), x.max(), 100)
plt.hist(x, bins=8, normed=True)
plt.plot(xgrid, density(xgrid), 'r-')
```

### Exercise: Comparative Chopstick Effectiveness

A few researchers set out to determine what the optimal length for chopsticks is. The dataset `chopstick-effectiveness.csv` includes measurements of "Food Pinching Efficiency" across a range of chopstick lengths for 31 individuals.

Use the method of moments or MLE to calculate the mean and variance of food pinching efficiency for each chopstick length. This means you need to select an appropriate distributional form for this data.

```python
# Write your answer here
```


#### Procesamiento Digital de Señales

# Trabajo Práctico Nº0
#### Yanina Corsaro

# Introducción
Jupyter Notebook es una herramienta para la confección de reportes técnicos, dado que permite la interacción en el mismo ambiente de:
1. un procesador de texto elemental (formato Markdown) que permite resaltar texto, en forma de *itálica* o **negrita** de manera muy legible (haciendo doble click en este texto podrás ver el código fuente estilo Markdown). Cuenta con estilos predefinidos:

# Título 1
## Título 2
### Título 3

y también la capacidad de incluir enlaces a otras páginas, como por ejemplo [esta página](https://medium.com/ibm-data-science-experience/markdown-for-jupyter-notebooks-cheatsheet-386c05aeebed) donde encontrarás más funcionalidades del lenguaje **Markdown**

2. capacidad para incluir lenguaje matemático estilo LaTex, tanto de forma presentada

\begin{equation}
T(z) = \frac{Y(z)}{X(z)} = \frac{ b_2 \, z^{-2} + b_1 \,  z^{-1} + b_0 }
{a_2 \, z^{-2} + a_1 \,  z^{-1} + a_0}
\end{equation}

como *inline* en el propio párrafo $y[k] = \frac{1}{a_0} \left( \sum_{m=0}^{M} b_m \; x[k-m] - \sum_{n=1}^{N} a_n \; y[k-n] \right) $

3. La posibilidad de incluir scripts en Python, como los que usaremos para las simulaciones en los TPs de la materia. En este caso usaremos el *testbench0.py* como ejemplo. Una vez que lo probamos y estamos seguros que funciona de forma esperada en *Spyder*, podemos incluir los resultados de la simulación de manera casi transparente. Solo tenemos que agregar una celda de código donde incluimos el código, y los resultados directamente quedan incluidos en este documento.

```python
# Módulos para Jupyter
import warnings
warnings.filterwarnings('ignore')

import numpy as np
import matplotlib as mpl
#%%  Inicialización de librerías
# Setup inline graphics: Esto lo hacemos para que el tamaño de la salida,
# sea un poco más adecuada al tamaño del documento
mpl.rcParams['figure.figsize'] = (10,10)

import matplotlib.pyplot as plt
import pdsmodulos as pds

#%% Esto tiene que ver con cuestiones de presentación de los gráficos,
# NO ES IMPORTANTE
fig_sz_x = 14
fig_sz_y = 13
fig_dpi = 80 # dpi

fig_font_family = 'Ubuntu'
fig_font_size = 16

plt.rcParams.update({'font.size':fig_font_size})
plt.rcParams.update({'font.family':fig_font_family})

##############################################
#%% A partir de aquí comienza lo IMPORTANTE #
#############################################

def my_testbench( sig_type ):

    # Datos generales de la simulación
    fs = 1000.0 # frecuencia de muestreo (Hz)
    N = 1000   # cantidad de muestras

    ts = 1/fs # tiempo de muestreo
    df = fs/N # resolución espectral

    # grilla de sampleo temporal
    tt = np.linspace(0, (N-1)*ts, N).flatten()

    # grilla de sampleo frecuencial
    ff = np.linspace(0, (N-1)*df, N).flatten()

    # Concatenación de matrices:
    # guardaremos las señales creadas al ir poblando la siguiente matriz vacía
    x = np.array([], dtype=np.float).reshape(N,0)
    ii = 0

    # estructuras de control de flujo
    if sig_type['tipo'] == 'senoidal':

        # calculo cada senoidal de acuerdo a sus parámetros
        for this_freq in sig_type['frecuencia']:
            # prestar atención que las tuplas dentro de los diccionarios también pueden direccionarse mediante "ii"
            aux = sig_type['amplitud'][ii] * np.sin( 2*np.pi*this_freq*tt + sig_type['fase'][ii] )
            # para concatenar horizontalmente es necesario cuidar que tengan iguales FILAS
            x = np.hstack([x, aux.reshape(N,1)] )
            ii += 1

    elif sig_type['tipo'] == 'ruido':

        # calculo cada señal de ruido incorrelado (blanco), Gausiano de acuerdo a sus parámetros
        # de varianza
        for this_var in sig_type['varianza']:
            aux = np.sqrt(this_var) * np.random.randn(N,1)
            # para concatenar horizontalmente es necesario cuidar que tengan iguales FILAS
            x = np.hstack([x, aux] )

        # Podemos agregar algún dato extra a la descripción de forma programática
        # {0:.3f} significa 0: primer argunmento de format
        # .3f formato flotante, con 3 decimales
        # $ ... $ indicamos que incluiremos sintaxis LaTex: $\hat{{\sigma}}^2$
        sig_props['descripcion'] = [ sig_props['descripcion'][ii] + ' - $\hat{{\sigma}}^2$ :{0:.3f}'.format( np.var(x[:,ii]))  for ii in range(0,len(sig_props['descripcion'])) ]

    else:

        print("Tipo de señal no implementado.")
        return

    #%% Presentación gráfica de los resultados

    plt.figure(1)
    line_hdls = plt.plot(tt, x)
    plt.title('Señal: ' + sig_type['tipo'] )
    plt.xlabel('tiempo [segundos]')
    plt.ylabel('Amplitud [V]')
    #    plt.grid(which='both', axis='both')

    # presentar una leyenda para cada tipo de señal
    axes_hdl = plt.gca()

    # este tipo de sintaxis es *MUY* de Python
    axes_hdl.legend(line_hdls, sig_type['descripcion'], loc='upper right'  )

    plt.show()

```

Dado que nuestro *testbench* ha sido desarrollado de manera funcional, llamando a la función *my_testbench()* con diferentes parámetros, podemos lograr funcionalidades diferentes, como mostramos a continuación primero con una senoidal:

```python
sig_props = { 'tipo': 'senoidal',
              'frecuencia': (3, 10, 20), # Uso de tuplas para las frecuencias
              'amplitud':   (1, 1,  1),
              'fase':       (0, 0,  0)
             }
sig_props2 = { 'tipo': 'senoidal',
              'frecuencia': (3, 10, 20), # Uso de tuplas para las frecuencias
              'amplitud':   (1, 1,  1),
              'fase':       (10, 5,  0)
             }
# Como también puedo agregar un campo descripción de manera programática
# este tipo de sintaxis es *MUY* de Python
sig_props['descripcion'] = [ str(a_freq) + ' Hz' for a_freq in sig_props['frecuencia'] ]
sig_props2['descripcion'] = [ str(a_freq) + ' Hz' for a_freq in sig_props['frecuencia'] ]
# Invocamos a nuestro testbench exclusivamente:
my_testbench( sig_props )
my_testbench( sig_props2 )
```

Y ahora con una señal aleatoria, en este caso ruido blanco Gaussiano incorrelado de varianza $\sigma^2$:

```python
# Usar CTRL+1 para comentar o descomentar el bloque de abajo.
sig_props = { 'tipo': 'ruido',
              'varianza': (1, 1, 1) # Uso de tuplas para las frecuencias
             }
sig_props['descripcion'] = [ '$\sigma^2$ = ' + str(a_var) for a_var in sig_props['varianza'] ]

# Invocamos a nuestro testbench exclusivamente:
my_testbench( sig_props )

```

Como puede verse en la figura anterior, al samplear una distribución estadística de media nula y varianza $\sigma^2=1$, obtenemos realizaciones cuyo parámetro $\sigma^2$ estimado, es decir $\hat\sigma^2$, tienen una desviación respecto al verdadero valor (sesgo). Nos ocuparemos de estudiar el sesgo y la varianza de algunos estimadores cuando veamos **Estimación Espectral**.

# Una vez terminado ...
Una vez que hayas termiando con la confección del documento, podemos utilizar una ventaja muy importante de este tipo de documentos que es la posibilidad de compartirlos *online* mediante la [página de nbviewer](http://nbviewer.jupyter.org/). Para ello es necesario que tu notebook y todos los recursos asociados estén alojados en un repositorio de [Github](https://github.com/). Como ejemplo, podés ver este mismo documento disponible [online](http://nbviewer.jupyter.org/github/marianux/pdstestbench/blob/master/notebook0.ipynb).

# Título de prueba

Agregué mi nombre y corrí el código para que aparezcan las imágenes. También agregue otra señal cambiando arbitrariamente la fase.