# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     name: python3
# ---

# + [markdown] id="LYvAOR2VzHmW"
#
# **Diplomatura en Ciencia de Datos, Aprendizaje Automático y sus Aplicaciones**
#
# **Edición 2022**
#
# ---
#
# # Datos y Modelos
#

# + id="Xwdfo7z20TUK"
import io
import matplotlib
import matplotlib.pyplot as plt
import numpy
import pandas as pd
import seaborn

seaborn.set_context('talk')

# + [markdown] id="XY2Hl-Ma07Nn"
# ## Lectura del dataset
#
# En la notebook 00 se explican los detalles de la siguiente sección.

# + id="Vviv_sqXdR5W"
url = 'https://cs.famaf.unc.edu.ar/~mteruel/datasets/diplodatos/sysarmy_survey_2020_processed.csv'
df = pd.read_csv(url)

# + id="gckNHXXLktJ4" colab={"base_uri": "https://localhost:8080/", "height": 323} executionInfo={"status": "ok", "timestamp": 1648060797106, "user_tz": 180, "elapsed": 139, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09936144167370803067"}} outputId="cbb3fa61-bf5f-4ae9-9881-0e4a2881ba73"
df[:3]

# + [markdown] id="2i_yGkkUs3QA"
# # Estadística descriptiva
#
# Hemos estado organizando y visualizando los datos de distintas maneras, pero ¿qué intuiciones podemos obtener?
#
# Las visualizaciones como histogramas o gráficos de conteo muestran la cantidad de veces que se observa cada valor en un conjunto de realizaciones de una variable aleatoria. Esto se denomina análisis de frecuencia, y es parte de la **estadística descriptiva**.
#
# El uso de visualizaciones nos limita a estimaciones, pero los datos crudos son demasiado como para intepretarlos en conjunto. Para eso, la estadística descriptiva provee también medidas de tendencia central y de dispersión, que resumen en un valor numérico propiedades de las realizaciones de la variable.
#
# Retomemos el problema original con la v.a. `salary_monthly_NETO`, ¿qué información brindan las siguientes métricas y cómo usarlas?
#

# + id="AXFDG0eBPDgH"
salary_col='salary_monthly_BRUTO'

# + colab={"base_uri": "https://localhost:8080/"} id="fHre-H9euQv4" executionInfo={"status": "ok", "timestamp": 1648063230485, "user_tz": 180, "elapsed": 436, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09936144167370803067"}} outputId="3ffe1c34-6492-40a2-e722-ec2bebbe2412"
df[salary_col].describe().apply(lambda s: '{0:.2f}'.format(s))

# + [markdown] id="QC-wJbBitzDH"
# ## Medidas de tendencia central
#
# Cuando se quiere obtener un valor representativo de todas las realizaciones de una v.a., o su centro, se utiliza una *medida de tendencia central*.
#
# Repasando, dada una característica de interés (modelada por X v.a.) y un conjunto de observaciones $x = \{ x_1, x_2 ... \}$ donde $x_i = X(\omega_i)$ para algún $\omega_i \in \Omega$, y $N = |x|$:
#
# * La **media muestral** (aritmética) o promedio se calcula como:
#
# $$ \bar{x} = \frac{1}{N} \sum_i^N x_i $$
#
# * La **mediana** se calcula:
#   1. Ordenar las realizaciones tal que $x_j \leq x_{j+1}$ 
#   2. Si la cantidad de datos $N$ es impar, la mediana es el valor central: $median = x_{\lfloor N / 2 \rfloor +1}$
#   3. Si la cantidad de datos $N$ es par, la mediana es e promedio de los dos valores centrales: $median = \frac{1}{2} (x_{ N / 2 } + x_{ (N / 2) +1})$
#
# * La **moda** son los valores o él valor con mayor frecuencia, es decir, los o él que más se repite.
#

# + colab={"base_uri": "https://localhost:8080/"} id="VGJfjf-x5TOh" executionInfo={"status": "ok", "timestamp": 1648063239575, "user_tz": 180, "elapsed": 325, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09936144167370803067"}} outputId="c109f974-16ac-4ede-e3bd-5fbc8cd8c9e5"
df[salary_col].mean(), df[salary_col].median()

# + [markdown] id="hDltOaTjnuFd"
# **¿Por qué las dos medidas son tan distintas?**
#
# * La media se puede interpretar como el *centro de masa* del histograma. Es decir, si el histograma fuera una figura de madera, el punto de equilibrio donde podemos apoyarlo y no se cae es la media.
# * La media es muy sensible a valores extremos. 
# * La mediana es más robusta a valores extremos.
# * Si la distribución de los datos es simétrica, las medidas coinciden. (Luego, si no coinciden es porque la distribución no es simétrica)
#
# **¿Se cumple para estos datos?**

# + colab={"base_uri": "https://localhost:8080/"} id="woWeBF8-0u5Q" executionInfo={"status": "ok", "timestamp": 1648063253444, "user_tz": 180, "elapsed": 323, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09936144167370803067"}} outputId="d0ffcb4a-1810-4d38-f357-c101dfdb393a"
max_salaries = [df[salary_col].max(), 10**6, 500000, 400000, 300000, 200000]
central_tendency = [
    (max_salary, df[df[salary_col] < max_salary][salary_col].mean(),
     df[df[salary_col] < max_salary][salary_col].median())
    for max_salary in max_salaries
]
central_tendency

# + [markdown] id="EZrjSY4yPV8-"
# Se pueden graficar estos números para lograr una mejor intuición de la magnitud de las diferencias. Además, al mostrar una visualización se pueden incluir más puntos.
#
# Para poder crear gráficos de seaborn con distintos grupos de datos, muchas veces es necesario cambiar el formato del dataframe de wide a long. Ver [este link](https://anvil.works/blog/tidy-data) para más información.

# + colab={"base_uri": "https://localhost:8080/", "height": 141} id="MpMJWSNq3Xq_" executionInfo={"status": "ok", "timestamp": 1618615228626, "user_tz": 180, "elapsed": 7815, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgEpHkJ3bTs6Ms1_5XLJaQvEwE5hv2Ac2i3p5w_Q2o=s64", "userId": "10886139577622185878"}} outputId="c2282517-899c-426c-b822-fdb4a9eb3a56"
central_tendency_max = [
    (max_salary, df[df[salary_col] < max_salary][salary_col].mean(),
     df[df[salary_col] < max_salary][salary_col].median())
    for max_salary in range(50000, int(df[salary_col].max()), 10**4)
]
central_tendency_max_df = pd.DataFrame(central_tendency_max,
                                   columns=['max_salary', 'mean', 'median'])\
  .melt(id_vars='max_salary', var_name='metric')

central_tendency_max_df[:3]

# + colab={"base_uri": "https://localhost:8080/", "height": 355} id="rJQfOlKV15Z4" executionInfo={"status": "ok", "timestamp": 1618615229342, "user_tz": 180, "elapsed": 8521, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgEpHkJ3bTs6Ms1_5XLJaQvEwE5hv2Ac2i3p5w_Q2o=s64", "userId": "10886139577622185878"}} outputId="7c393e3a-4b53-4432-b487-79fef379f2ef"
# ¡Podemos ver estos datos visualmente! valga la redundancia!!
fig = plt.figure(figsize=(15, 5))
seaborn.lineplot(data=central_tendency_max_df, x='max_salary', y='value', hue='metric')
plt.ticklabel_format(style='plain', axis='x')
seaborn.despine()

# + colab={"base_uri": "https://localhost:8080/", "height": 519} id="mSyyNgvndRPQ" executionInfo={"status": "ok", "timestamp": 1618615230016, "user_tz": 180, "elapsed": 9185, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgEpHkJ3bTs6Ms1_5XLJaQvEwE5hv2Ac2i3p5w_Q2o=s64", "userId": "10886139577622185878"}} outputId="4c4037e0-1e16-40ba-b6c1-0468e9329470"
## La diferencia no siempre se aprecia en los gráficos
fig, axes = plt.subplots(nrows=2, figsize=(16, 8))
seaborn.histplot(df[salary_col], bins=100, ax=axes[0], color='gray')
axes[0].axvline(df[salary_col].mean(), color='orangered',
            linestyle='--', label='Media')
axes[0].axvline(df[salary_col].median(), color='indigo',
            linestyle='-.', label='Mediana')

filtered_df = df[df[salary_col] < 200000]
seaborn.histplot(filtered_df[salary_col], bins=100, ax=axes[1], color='gray')
axes[1].axvline(filtered_df[salary_col].mean(), color='orangered',
            linestyle='--', label='Media')
axes[1].axvline(filtered_df[salary_col].median(), color='indigo',
            linestyle='-.', label='Mediana')

axes[0].legend()
seaborn.despine()

# + [markdown] id="3MdG-7bK8AKR"
# ¿Qué decir de la moda? Sólo que el resultado de la función no es un valor, sino una series de valores, aunque la serie tenga un único elemento.

# + colab={"base_uri": "https://localhost:8080/"} id="r01xw1q18AmV" executionInfo={"status": "ok", "timestamp": 1618615230021, "user_tz": 180, "elapsed": 9178, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgEpHkJ3bTs6Ms1_5XLJaQvEwE5hv2Ac2i3p5w_Q2o=s64", "userId": "10886139577622185878"}} outputId="ca25ecd4-6b27-4615-a64c-edeaefa03673"
df.profile_gender.mode()

# + [markdown] id="Li3vLv3X8k7Z"
# ## Medidas de dispersión
#
# Las medidas de dispersión vistas en el teórico son la desviación estándar, la varianza, y el coeficiente de variación. También permiten representar con un número alguna propiedad de los datos.
#
# Por ejemplo, comparemos el salario neto con el salario bruto. A priori, **¿deberíamos ver alguna diferencia?**

# + colab={"base_uri": "https://localhost:8080/", "height": 295} id="504NtuJWjpX0" executionInfo={"status": "ok", "timestamp": 1618615230024, "user_tz": 180, "elapsed": 9170, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgEpHkJ3bTs6Ms1_5XLJaQvEwE5hv2Ac2i3p5w_Q2o=s64", "userId": "10886139577622185878"}} outputId="be2db5aa-afb4-4203-cd5b-5146992b08bb"
df[['salary_monthly_NETO', 'salary_monthly_BRUTO']].describe().round()

# + [markdown] id="m6dcAgVYlUWK"
# Claramente, ambas distribuciones están centradas en valores distintos, pero ¿podemos decir algo sobre su dispersión?
#
# Cuando se comparan dos características diferentes (que pueden tener magnitudes diferentes) puede no ser conveniente comparar directamente los valores de las desviaciones estándar, sino que podemos usar el coeficiente de variación (desviación estándar dividida la media).

# + colab={"base_uri": "https://localhost:8080/"} id="5Ga3FpQalrCm" executionInfo={"status": "ok", "timestamp": 1618615230025, "user_tz": 180, "elapsed": 9156, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgEpHkJ3bTs6Ms1_5XLJaQvEwE5hv2Ac2i3p5w_Q2o=s64", "userId": "10886139577622185878"}} outputId="40b738d6-04e5-4971-93fa-b0b0b55e3b39"
import scipy

print("Coeficiente de variación salario bruto",
      scipy.stats.variation(df.salary_monthly_BRUTO))
print("Coeficiente de variación salario neto",
      scipy.stats.variation(df.salary_monthly_NETO.dropna()))

# + [markdown] id="lVG6Ro-6ao3j"
# ## Percentiles y gráficos de caja
#
# Los gráficos de caja son otra forma de representar la distribución de las realizaciones de una v.a. numérica, de una forma más condensada que un histograma.
#
# Son muy útiles para comparar muchas distribuciones, pero sólo cuando son muy distintas entre ellas, ya que oscurecen algunas sutilezas. Otros problema de este tipo de gráficos es que *no todo el mundo recuerda cómo leerlos*.
#
# En estadística descriptiva, un gráfico de caja es un método para representar gráficamente grupos de datos numéricos a través de sus cuartiles. Los gráficos de caja también pueden tener líneas que se extienden verticalmente desde las cajas (bigotes) indicando la variabilidad fuera de los cuartiles superior e inferior. Los valores atípicos pueden representarse como puntos individuales.
#
# La definición anterior sugiere que, si hay un valor atípico, se representará como un punto en el diagrama de caja, mientras que el resto de los datos de la muestra se agrupará y se mostrará en forma de cajas. Intentemos verlo nosotros mismos.
# <center>
# <img src="https://miro.medium.com/max/1400/1*2c21SkzJMf3frPXPAR_gZA.png" height="300" />
# </center>

# + colab={"base_uri": "https://localhost:8080/", "height": 301} id="5dbBiShrasMI" executionInfo={"status": "ok", "timestamp": 1618615230681, "user_tz": 180, "elapsed": 9794, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgEpHkJ3bTs6Ms1_5XLJaQvEwE5hv2Ac2i3p5w_Q2o=s64", "userId": "10886139577622185878"}} outputId="d88536fb-a803-43d2-d9f7-74b5a54bc231"
plt.figure(figsize=(12, 4))
seaborn.boxplot(x=df[salary_col])
seaborn.despine()

# + id="j9J3KNTD_S6j" colab={"base_uri": "https://localhost:8080/", "height": 372} executionInfo={"status": "ok", "timestamp": 1618615309974, "user_tz": 180, "elapsed": 930, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgEpHkJ3bTs6Ms1_5XLJaQvEwE5hv2Ac2i3p5w_Q2o=s64", "userId": "10886139577622185878"}} outputId="68157738-fbd1-4296-bf9f-b5899f6c9714"
seaborn.distplot(df[df.profile_age < 100].profile_age)

# + colab={"base_uri": "https://localhost:8080/", "height": 318} id="GdK00mpDa7Nz" executionInfo={"status": "ok", "timestamp": 1618330973435, "user_tz": 180, "elapsed": 8844, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14Gj5Cl9j0p5Ont2qFoH4hE2P4Pws7ek_gATkrdVJ=s64", "userId": "13159704290090351697"}} outputId="c5431d14-a5f9-472c-f6f8-e7e0be5be086"
plt.figure(figsize=(12, 4))
seaborn.boxplot(x=df[df.profile_age < 100].profile_age)

# + [markdown] id="tyx3Pmk-dJL4"
# Por ejemplo, podemos comparar la distribución de los salarios netos con respecto al nivel de estudios alcanzado.

# + colab={"base_uri": "https://localhost:8080/", "height": 390} id="W1dKgRP9gkHj" executionInfo={"status": "ok", "timestamp": 1618330973438, "user_tz": 180, "elapsed": 8833, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14Gj5Cl9j0p5Ont2qFoH4hE2P4Pws7ek_gATkrdVJ=s64", "userId": "13159704290090351697"}} outputId="bc5078c3-3a8d-4136-9b49-99e04eac18ea"
plt.figure(figsize=(12, 6))
seaborn.boxplot(data=df, x=salary_col, y='profile_studies_level',
                color='orangered')
plt.ticklabel_format(style='plain', axis='x')

# + [markdown] id="HClH-TMBhMfg"
# **¿Qué puede estar causando una diferencia tan grande en la distribución para las respuestas que dicen Posdoctorado?**

# + [markdown] id="yqHgU6mUhXSi"
# ### Boxenplots
#
# Los boxplots tienen una gran desventaja: ocultan mucha información en la distribución de las colas. Por ejemplo, para la categoría Posdoctorado, sabemos que el 25% de los valores de sueldo neto es mayor que los ~650000 pesos. Pero no conocemos cómo se distribuyen. Para conjuntos de datos de gran tamaño, el 25% de los datos contiene mucha información.
#
# Un gráfico más informativo es el **boxenplot**, que visualiza más percentiles. Otra ventaja es la percepción del mismo debido al peso visual de las cajas: los datos en el rango intercuartílico no parecen muuuucho más importantes que los datos en las colas.
#
# Sin embargo, es aún más difícil de leer si buscamos exactitud, ya que los percentiles que definen el límite de cada caja se definen recursivamente y no decrecen linealmente.

# + colab={"base_uri": "https://localhost:8080/", "height": 410} id="evtF2AFChc06" executionInfo={"status": "ok", "timestamp": 1648064038381, "user_tz": 180, "elapsed": 1058, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09936144167370803067"}} outputId="8bfcaa03-5a48-4bde-a5df-b3b1b9a900b7"
plt.figure(figsize=(12, 6))
seaborn.boxenplot(data=df, x=salary_col, y='profile_studies_level',
                color='orangered')
plt.ticklabel_format(style='plain', axis='x')

# + [markdown] id="81z4Ue6PkEZr"
# ## Eliminación de valores extremos
#
# ### Usando percentiles 
#
# Una forma conservadora de eliminar valores extremos que estén afectando a la media, el rango y las visualizaciones es seleccionar un cierto porcentaje más extremo. Para eso, usamos los percentiles.
#
# Por ejemplo, podemos elegir quedarnos con el 99% de salarios más bajos, eliminando el 1%. Podemos calcular todos los percentiles para decidir cuál sería el más apropiado.

# + colab={"base_uri": "https://localhost:8080/"} id="-R7cIusV_1ri" executionInfo={"status": "ok", "timestamp": 1619706141046, "user_tz": 180, "elapsed": 887, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14Gj5Cl9j0p5Ont2qFoH4hE2P4Pws7ek_gATkrdVJ=s64", "userId": "13159704290090351697"}} outputId="f4c1242f-b76e-431c-e219-d074aa6e3a06"
k = 90
percentile_90 = df[salary_col].quantile(k / 100)
n_below = len(df[df[salary_col] < percentile_90])
n_above = len(df[df[salary_col] > percentile_90])
print('Percentil {} de la columna {}: {}'.format(k, salary_col, percentile_90))
print('% de datos menor que percentil {}: {}'.format(k, n_below / len(df)))
print('% de datos mayor que percentil {}: {}'.format(k, n_above / len(df)))

# + colab={"base_uri": "https://localhost:8080/"} id="bWEgaBVvka9p" executionInfo={"status": "ok", "timestamp": 1618330974254, "user_tz": 180, "elapsed": 9628, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14Gj5Cl9j0p5Ont2qFoH4hE2P4Pws7ek_gATkrdVJ=s64", "userId": "13159704290090351697"}} outputId="e7baf270-f52d-4ec8-8aa6-dce26d5584dd"
df[salary_col].quantile([.95, .98, .99, .995, .998])

# + colab={"base_uri": "https://localhost:8080/", "height": 318} id="A-2cG3unruwo" executionInfo={"status": "ok", "timestamp": 1618330974258, "user_tz": 180, "elapsed": 9619, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14Gj5Cl9j0p5Ont2qFoH4hE2P4Pws7ek_gATkrdVJ=s64", "userId": "13159704290090351697"}} outputId="2e8308b6-f072-40ae-94c2-a84c123e8618"
plt.figure(figsize=(12, 4))
max_limit = df[salary_col].quantile(.98)
seaborn.boxenplot(x=df[df[salary_col] < max_limit][salary_col])

# + colab={"base_uri": "https://localhost:8080/", "height": 753} id="MWmor0akspwt" executionInfo={"status": "ok", "timestamp": 1618330975030, "user_tz": 180, "elapsed": 10377, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14Gj5Cl9j0p5Ont2qFoH4hE2P4Pws7ek_gATkrdVJ=s64", "userId": "13159704290090351697"}} outputId="c07bcbd1-fb54-4b28-97a8-63df74c03b08"
fig, axes = plt.subplots(figsize=(12, 12), nrows=3)
max_limit = df[salary_col].quantile(.98)
data = df[df[salary_col] < max_limit][salary_col]
seaborn.histplot(x=data, ax=axes[0])
seaborn.boxplot(x=data, ax=axes[1])
seaborn.boxenplot(x=data, ax=axes[2])


# + [markdown] id="SBkKwLFltJwg"
# ### <NAME>

# + id="poVH7-0RFxqC"
def clean_outliers_q3(dataset, column_name):
    """Returns dataset removing the outlier rows from column @column_name."""
    interesting_col = dataset[column_name]
    # Here we can remove the outliers from both ends, or even add more restrictions.
    mask_outlier = (interesting_col <= (2.5 * interesting_col.quantile(.75)))
    return dataset[mask_outlier]


# + colab={"base_uri": "https://localhost:8080/", "height": 319} id="MaZj8_fatXgo" executionInfo={"status": "ok", "timestamp": 1648064273907, "user_tz": 180, "elapsed": 1515, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09936144167370803067"}} outputId="86e80326-c724-4c21-c1de-b3c867c97e1a"
fig = plt.figure(figsize=(12, 4))
data = clean_outliers_q3(df, salary_col)[salary_col]
seaborn.histplot(x=data)


# + id="9RPNlz5-kjgD"
def clean_outliers_sd(dataset, column_name):
    """Returns dataset removing the outlier rows from column @column_name."""
    interesting_col = dataset[column_name]
    # Here we can remove the outliers from both ends, or even add more restrictions.
    mask_outlier = (
        numpy.abs(interesting_col - interesting_col.mean()) <=
        (2.5 * interesting_col.std()))
    return dataset[mask_outlier]


# + colab={"base_uri": "https://localhost:8080/", "height": 319} id="bpQ28UwSGYAF" executionInfo={"status": "ok", "timestamp": 1648064304620, "user_tz": 180, "elapsed": 521, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09936144167370803067"}} outputId="1269dc58-75be-4ca3-ef51-0bc241ee8cfe"
fig = plt.figure(figsize=(12, 4))
data = clean_outliers_sd(df, salary_col)[salary_col]
seaborn.histplot(x=data)

# + [markdown] id="KY55pa57CW7T"
#

# + [markdown] id="fuDscbVqttGZ"
# ### ¡Mirando los datos!
#
# ¿Quiénes son los que cobran tanto?

# + colab={"base_uri": "https://localhost:8080/", "height": 862} id="zIt2nJXvtx3g" executionInfo={"status": "ok", "timestamp": 1618330975464, "user_tz": 180, "elapsed": 10785, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14Gj5Cl9j0p5Ont2qFoH4hE2P4Pws7ek_gATkrdVJ=s64", "userId": "13159704290090351697"}} outputId="68b04aad-5eca-4b3f-c3eb-4fdd61e5387f"
df[df[salary_col] > df[salary_col].quantile(0.98)]

# + [markdown] id="SfgRCnKUaUBH"
# Volvemos a las filminas

# + [markdown] id="s-VSiRuLCXxg"
# Datos vs Modelo
#
# QQplot

# + id="2lzmzK1NuPNT"


StarcoderdataJupyterScriptsDedupFiltered