<jupyter_start><jupyter_text>MAT281 Aplicaciones de la Matemática en la Ingeniería  Tarea Módulo 02  Instrucciones* Completa tus datos personales (nombre y rol USM) en siguiente celda.* Debes _pushear_ este archivo con tus cambios a tu repositorio personal del curso, incluyendo datos, imágenes, scriptsm, etc.* Como respaldo, debes enviar un archivo .zip con el siguiente formato `m02_homework_apellido_nombre.zip` a <EMAIL>, debe contener todo lo necesario para que se ejecute correctamente cada celda, ya sea datos, imágenes, scripts, etc.* Se evaluará:    - Soluciones    - Código    - Que Binder esté bien configurado.    - Al presionar  `Kernel -> Restart Kernel and Run All Cells` deben ejecutarse todas las celdas sin error. __Nombre__:__Rol__:  Ejercicio 1: ImagenceptionDesde [Wikipedia](https://es.wikipedia.org/wiki/RGB), __RGB__ (sigla en inglés de red, green, blue) es un modelo de color basado en la síntesis aditiva, con el que es posible representar un color mediante la mezcla por adición de los tres colores de luz primarios. El modelo de color RGB no define por sí mismo lo que significa exactamente rojo, verde o azul, por lo que los mismos valores RGB pueden mostrar colores notablemente diferentes en distintos dispositivos que usen este modelo de color. Aunque utilicen un mismo modelo de color, sus espacios de color pueden variar considerablemente.Para indicar con qué proporción es mezclado cada color, se asigna un valor a cada uno de los colores primarios, de manera que el valor "0" significa que no interviene en la mezcla y, a medida que ese valor aumenta, se entiende que aporta más intensidad a la mezcla. Aunque el intervalo de valores podría ser cualquiera (valores reales entre 0 y 1, valores enteros entre 0 y 37, etc.), es frecuente que cada color primario se codifique con un byte (8 bits).Así, de manera usual, la intensidad de cada una de las componentes se mide según una escala que va del 0 al 255 y cada color es definido por un conjunto de valores escritos entre paréntesis (correspondientes a valores "R", "G" y "B") y separados por comas.El conjunto de todos los colores también se puede representar en forma de cubo. Cada color es un punto de la superficie o del interior de éste. La escala de grises estaría situada en la diagonal que une al color blanco con el negro.![rgb](https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Avl3119color4a.svg/800px-Avl3119color4a.svg.png) Para efectos prácticos del curso, es posible representar cada pixel de una imagen con un array de 3 dimensiones, cada valor representa a una de las capas RGB. Por lo tanto, una imagen de $n \times m$ pixeles se representa como un arreglo de dimension $(n, m , 3)$ En `Python` una de las librerías de procesamiento de imágenes más utilizada es `Pillow`.  Instalación de libreríasLo primero es instalar la libreria `Pillow` e importar las librerías que se utilizarán. Para la instalación basta con ejecutar el siguiente comando en tu terminal:```bashconda install -n mat281 pillow```El código anterior instala la librería `pillow` en el entorno virtual `mat281` (el nombre del archivo `environment.yml` disponibilizado en el repositorio del curso).<jupyter_code>import os
import numpy as np
from PIL import Image<jupyter_output><empty_output><jupyter_text>Abrir una imagen es tan fácil como<jupyter_code>gatito = Image.open(os.path.join("images", "gatito.png"))<jupyter_output><empty_output><jupyter_text>Notar que la variable anterior es de una clase específica de la librería.<jupyter_code>type(gatito)<jupyter_output><empty_output><jupyter_text>Para ver la imagen en Jupyter puedes utilizar la misma técnica que con los `pd.DataFrames`, es decir:<jupyter_code>gatito<jupyter_output><empty_output><jupyter_text>Para tener su representación en un array podemos utilizar el constructor `np.array` con argumento la imagen.<jupyter_code>gatito_np = np.array(gatito)
print(f"Dimension de la imagen gatito: {gatito_np.shape}.\n")
print(f"Al convertir a np.ndarry el tipo de elementos es {gatito_np.dtype}.\n")
print(gatito_np)<jupyter_output><empty_output><jupyter_text>Encontrando la imagen oculta (35 pts)La imagen anterior tiene una imagen oculta, el ejercicio corresponde en descifrarlo. Las instrucciones son las siguientes: 1. Crear una lista vacía declarada como `secret_list`.<jupyter_code>secret_list = ## FIX ME ##<jupyter_output><empty_output><jupyter_text>2. Iterar por cada uno de los canales RGB y en cada iteración:    * Crear un arreglo temporal llamado `secret_aux` de dos dimensiones, de la misma dimension de pixeles de la imagen `gatito` y que tenga valores enteros, `0` si el valor de la capa de `gatito_np` es par y `1` si es impar.         - No iterar por filas y columnas.        - Utilizar la operación módulo `%`.         - En la i-ésima iteración de los canales la capa de `gatito_np` es `gatito_np[:, :, i]`.    * Escalar `secret_aux` a valores 0 y 255.    * Cambiar el `dtype` de `secret_aux` a `np.uint8`.        - Utilize el méteodo `astype()`.    * Agregue `secret_aux` a `secret_list`.    Al final de la iteración `secret_list` debe tener solo tres arreglos.<jupyter_code>for channel in ## FIX ME ##:
    secret_aux = ## FIX ME ##
    secret_aux = ## FIX ME ##
    secret_aux = ## FIX ME ##
    secret_list.append(secret_aux)
print(f"secret_list tiene {len(secret_list)} elementos")<jupyter_output><empty_output><jupyter_text>3. Crear la variable `secret_np` concatenando horizontalmente los elementos de `secret_list`.<jupyter_code>secret_np = ## FIX ME ##
secret_np.shape<jupyter_output><empty_output><jupyter_text>4. Crear el objeto `secret_img` utilizando el arreglo `secret_np`, asegurar que los valores estén entre 0 y 255, y que el dtype sea `np.uint8`, con el método `Image.fromarray` con argumento `mode="L"`<jupyter_code>np.unique(secret_np)
secret_np.dtype
secret_img = Image.fromarray(## FIX ME ##)<jupyter_output><empty_output><jupyter_text>Ahora puedes ver el resultado!<jupyter_code>secret_img<jupyter_output><empty_output><jupyter_text>Escondiendo una nueva imagen (25 pts)Es tu turno, ahora tu esconderás una imagen. Las instrucciones son las siguientes:<jupyter_code>secret_np.shape<jupyter_output><empty_output><jupyter_text>1. Selecciona una imagen de 2160 x 3840 pixeles (a.k.a resolución 4k), lo importante es que sea solo en blanco y negro, en la carpeta `images` se disponibiliza como ejemplo la imagen `black_and_white_example.jpg` y crea una variable llamada `my_img` leyendo la imagen seleccionada con `Image.open()`.<jupyter_code>my_img = Image.open(## FIX ME ##)<jupyter_output><empty_output><jupyter_text>2. Crea un arreglo llamado `my_img_np` utilizando `my_img` y el método `np.array()`.    * Es importante que `my_img_np.shape` sea `(2160, 3840)`, es decir, que solo sea de dos dimensiones. Esto porque es una imagen en blanco y negro, no necesitando el modelo RGB.<jupyter_code>my_img_np = ## FIX ME ##
print(my_img_np.shape)<jupyter_output><empty_output><jupyter_text>3. Crear la variable `my_img_np_aux` utilizando un _umbral_ con tal de que:    - 1: Si el valor del pixel es mayor  al _umbral_.    - 0: Si el valor del pixel es menor o igual al _umbral_.    - El `dtype` debe ser `np.uint8`.    - Para `black_and_white_example.jpg` un umbral adecuado es `20`.<jupyter_code>umbral = 20
my_img_np_aux = ## FIX ME ##<jupyter_output><empty_output><jupyter_text>Puedes probar que tan bien quedó la imagen con la siguiente linea. Si crees que no se ve bien, puedes cambiar el _umbral_.<jupyter_code>Image.fromarray(my_img_np_aux * 255)<jupyter_output><empty_output><jupyter_text>4. Dividir la imagen en tres arreglos de tamaño (2160, 1280) y guardarlos en una lista con el nombre `my_img_split`. Hint: Revisa en la documentación de `numpy`.<jupyter_code>my_img_split = ## FIX ME ##<jupyter_output><empty_output><jupyter_text>Revisa utilizando la siguiente iteración.<jupyter_code>for img_array in my_img_split:
    print(img_array.shape)<jupyter_output><empty_output><jupyter_text>5. La imagen donde se esconderá tu imagen selecionada está en la carpeta `images` con el nombre `gatito_original.png`, que sospechosamente es de 2160 x 1280 pixeles. Carga la imagen en la variable `cat` y luego crea arreglo `cat_np` utilizando `cat`. Verifica que `cat_np.shape = (2160, 1280, 3)`.<jupyter_code>cat = Image.open(## FIX ME ##)
cat_np = ## FIX ME ##
print(cat_np.shape)<jupyter_output><empty_output><jupyter_text>6. Convierte todos los valores de `cat_np` a valores pares. Esto lo puedes hacer sumando 1 a cada valor de arreglo si es impar.<jupyter_code>cat_np ## FIX ME ##<jupyter_output><empty_output><jupyter_text>7. Itera por canal RGB de `cat_np` y en cada capa suma los valores de uno de los arreglos de `my_img_split`.<jupyter_code>for channel in ## FIX ME ##:
    cat_np[## FIX ME ##] += ## FIX ME ##<jupyter_output><empty_output><jupyter_text>8. Crea una variable llamada `cat_secret_im` con `Image.fromarray` y la variable `cat_np` (que ya ha sido modificada). Luego guarda la imagen en la carpeta `images` con el nombre `my_secret.png`.<jupyter_code>cat_secret_im = Image.fromarray(## FIX ME ##)
cat_secret_im.save(## FIX ME ##)<jupyter_output><empty_output><jupyter_text>9. Crea una función llamada `imagenception()` que como argumento tenga la ruta de la imagen que quieres descifrar y que descifre la imagen secreta recientemente creada. Hint: Utiliza todos los pasos de la primera parte.<jupyter_code>def imagenception(filepath):
   ## FIX ME ##
    return secret_img
my_secret_img = imagenception(os.path.join("images", "my_secret.png"))
my_secret_img<jupyter_output><empty_output><jupyter_text>Ejercicio 2: Analizando la FelicidadEste ejercicio es netamente análisis de datos, tratando de abarcar problemas típicos como la lectura de datos, corrección de errores, métricas agrupadas, unión de datos, etc. Utilizaremos un conjunto de datos llamado __World Happiness Report__ disponible en el siguiente [link](https://www.kaggle.com/unsdsn/world-happiness), de donde se puede obtener información al respecto. ContextThe World Happiness Report is a landmark survey of the state of global happiness. The first report was published in 2012, the second in 2013, the third in 2015, and the fourth in the 2016 Update. The World Happiness 2017, which ranks 155 countries by their happiness levels, was released at the United Nations at an event celebrating International Day of Happiness on March 20th. The report continues to gain global recognition as governments, organizations and civil society increasingly use happiness indicators to inform their policy-making decisions. Leading experts across fields – economics, psychology, survey analysis, national statistics, health, public policy and more – describe how measurements of well-being can be used effectively to assess the progress of nations. The reports review the state of happiness in the world today and show how the new science of happiness explains personal and national variations in happiness. ContentThe happiness scores and rankings use data from the Gallup World Poll. The scores are based on answers to the main life evaluation question asked in the poll. This question, known as the Cantril ladder, asks respondents to think of a ladder with the best possible life for them being a 10 and the worst possible life being a 0 and to rate their own current lives on that scale. The scores are from nationally representative samples for the years 2013-2016 and use the Gallup weights to make the estimates representative. The columns following the happiness score estimate the extent to which each of six factors – economic production, social support, life expectancy, freedom, absence of corruption, and generosity – contribute to making life evaluations higher in each country than they are in Dystopia, a hypothetical country that has values equal to the world’s lowest national averages for each of the six factors. They have no impact on the total score reported for each country, but they do explain why some countries rank higher than others.  2.1 Lectura de datos<jupyter_code>import pandas as pd
pd.set_option("display.max_columns", 999)  # Permite mostrar hasta 999 columnas de un DataFrame en Jupyter.<jupyter_output><empty_output><jupyter_text>En la carpeta `data/world-happiness` se disponen de tres archivos, uno por cada reporte anual (años 2015, 2016 y 2017). No es de sorprender que envíen un archivo por año (podría ser mensual, semestral, etc.), lo imortante es ser capaces de leer una cantidad __variable__ de archivos al mismo tiempo. Una buena práctica es crear un diccionario de dataframes.<jupyter_code># Comprehension dictionary
df_dict = {
    year: pd.read_csv(os.path.join("data", "world-happiness", f"{year}.csv"))
    for year in [2015, 2016, 2017]
}<jupyter_output><empty_output><jupyter_text>Por ejemplo, se puede acceder al DataFrame asociado al archivo `data/world-happiness/2016.csv` de la siguiente manera:<jupyter_code>df_dict[2016].head()<jupyter_output><empty_output><jupyter_text>Una pequeña descripción de las columnas* `Country` Name of the country.* `Region` Region the country belongs to.* `Happiness Rank` Rank of the country based on the Happiness Score.* `Happiness Score` A metric measured in 2015 by asking the sampled people the question: "How would you rate your happiness on a scale of 0 to 10 where 10 is the happiest."* `Standard Error` The standard error of the happiness score.* `Economy (GDP per Capita)` The extent to which GDP contributes to the calculation of the Happiness Score.* `Family` The extent to which Family contributes to the calculation of the Happiness Score* `Health (Life Expectancy)` The extent to which Life expectancy contributed to the calculation of the Happiness Score* `Freedom` The extent to which Freedom contributed to the calculation of the Happiness Score.* `Trust (Government Corruption)` The extent to which Perception of Corruption contributes to Happiness Score.* `Generosity` The extent to which Generosity contributed to the calculation of the Happiness Score.* `Dystopia Residual` The extent to which Dystopia Residual contributed to the calculation of the Happiness Score. Notar que los conjuntos de datos no poseen las mismas columnas, por lo tanto, solo se trabajarán con las columnas en común y posteriormente agregaremos el año con tal de concatenar los tres conjuntos.<jupyter_code>from functools import reduce
intersection_columns = reduce(np.intersect1d, [df_i.columns.values for df_i in df_dict.values()]).tolist() + ["Year"]
print(intersection_columns)<jupyter_output><empty_output><jupyter_text>Explica con tus palabras las operaciones que se realizaron para obtener la variable `intersection_columns`. __Respuesta:__    2.2 Concatenación y procesado Define el DataFrame `happiness` tal que:* Sea la concatenación de los dataframes de `df_dict`    - Nota que en la documentación de `pd.concat` puedes entregar como argumento directamente un diccionario.    - No ordenes los _axis_ (ver documentación).    - Los nombres de los _levels_ para los multi-index resultante deben ser `["Year", "drop_me"]`.* Aplica el método `drop_level` con tal de eliminar el nivel del multi-index llamado `drop_me`.* Resetea los índices.* Selecciona solo las columnas de la lista `intersection_columns`.* Los nombres de las columnas deben estar en minísculas, reemplazar espacios por guiones bajos (`_`) y elimina los paréntesis.<jupyter_code>happiness = (
    pd.concat(## FIX ME ##)
    .droplevel(## FIX ME ##)
    .## FIX ME ##
    .## FIX ME ##
    .## FIX ME ##
)
happiness.head()<jupyter_output><empty_output><jupyter_text>2.3 Análisis Como siempre, partimos con un análisis descriptivo simple.<jupyter_code>happiness.describe(include="all").fillna("").T<jupyter_output><empty_output><jupyter_text>¿Cuántos países no tienen mediciones de felicidad en los tres años del estudio? ¿Cuáles son?<jupyter_code>## FIX ME ##
## FIX ME ##<jupyter_output><empty_output><jupyter_text>__Respuesta:__   Note que la lista de países proveniente de la pregunta anterior tiene errores de consistencia, por ejemplo están los registros de `Hong Kong` y `Hong Kong S.A.R., China` que escencialmente son el mismo. Lo mismo ocurre con `Taiwan` y `Somaliland Region`. Modifique la columna `country` del dataframe `happiness` con tal de reparar los errores de `Hong Kong`, `Taiwan` y `Somaliland Region`.<jupyter_code>bad_country_names_dict = {"Hong Kong S.A.R., China": "Hong Kong", ## FIX ME ##}
happiness = happiness.assign(## FIX ME ##)<jupyter_output><empty_output><jupyter_text>Luego de la modificación, ¿Cuántos países no tienen mediciones en los tres años de estudio?<jupyter_code>## FIX ME ##<jupyter_output><empty_output><jupyter_text>__Respuesta:__   Pivotea el dataframe `happines` tal que los índices sean los años, las columnas los países y el valor su `happiness_score`. LLena los valores nulos con un _string_ vacío `""`. Un país no puede tener más de un registro por año, por lo que puedes utilizar directamente el médoto `pd.DataFrame.pivot()`.<jupyter_code>## FIX ME ##<jupyter_output><empty_output><jupyter_text>¿Qué información podrías sacar rápidamente de esta tabla pivoteada? ¿Podrías decir que siempre es útil pivotear una tabla? __Respuesta:__   En promedio, ¿Cuáles son los tres países con el mejor ranking de felicidad?<jupyter_code>## FIX ME ##<jupyter_output><empty_output><jupyter_text>__Respuesta:__   En promedio, ¿Cuáles son los tres países con el mayor _score_ de felicidad? ¿Son distintos a los con mejor ranking en promedio?<jupyter_code>## FIX ME ##<jupyter_output><empty_output><jupyter_text>__Respuesta:__   Calcula el promedio anual de todas las columnas factores de felicidad, es decir, todas las variables numéricas excepto `happiness_score` y `happiness_rank`.<jupyter_code>hap_mean_factors = ## FIX ME ##
hap_mean_factors<jupyter_output><empty_output><jupyter_text>Respecto al cálculo anterior, para cada uno de los años, ¿Cuál es el factor que más contribuye (en promedio) al _score_ de la felicidad y en qué medida?<jupyter_code>## FIX ME ##<jupyter_output><empty_output><jupyter_text>__Resuesta:__* 2015:  * 2016:  * 2017:    2.4 Agregando más datos A continuación, agregaremos un nuevo conjunto de datos, el que contiene estadísticas de suicidio por años, países y rangos etáreos. Se encuentra disponible en el siguiente [link](https://www.kaggle.com/russellyates88/suicide-rates-overview-1985-to-2016).<jupyter_code>suicide = pd.read_csv(os.path.join("data", "suicide_rates.csv"))
suicide.head()<jupyter_output><empty_output><jupyter_text>La mayoría de las columnas son autoexplicativas.* `country`* `year`* `sex`* `age`* `suicides_no`* `population`* `suicides/100k pop`* `country-year`* `HDI for year` Human Development Index* `gdp_for_year ($)` Gross Domestic Product* `gdp_per_capita ($)`* `generation` based on age grouping average Un poco de estadística descriptiva.<jupyter_code>suicide.describe(include="all").fillna("").T<jupyter_output><empty_output><jupyter_text>Crea un nuevo DataFrame llamado suicide_agg siguiendo las siguientes instrucciones:* Agrupa por país y año.* Suma la población y el número de suicidios.* Resetea los índices.* Agrega una nueva columna llamada `suicides_ratio_100k` formada por la división de `suicides_no` y `population`, para posteriormente muliplicarla por 100,000.* Agrega una nuevale columna llamada `suicides_rank` similar a `happiness_rank`, es decir, que asigne un orden __por año__ a cada país según la columna `suicides_ratio_100k` tal que el rank 1 corresponda al que tenga mayor `suicides_ratio_100k`. Hint: Usa el método `rank()`.<jupyter_code># Es posible hacer todas las operaciones encadenadas!
suicides_agg = (
    suicide.groupby(## FIX ME ##])
    .agg(
      ## FIX ME ##
    )
    .## FIX ME ##
    .assign(
        suicides_ratio_100k=## FIX ME ##,
        suicides_rank=## FIX ME ##
    )
)<jupyter_output><empty_output><jupyter_text>Crea un nuevo DataFrame con el nombre `hap_sui` al unir `happiness` y `suicides_agg` tal que coincidan país y año, quédate con solo los registros que coincidan en ambas DataFrames.<jupyter_code>hap_sui = ## FIX ME ##
hap_sui.head()<jupyter_output><empty_output><jupyter_text>¿Qué tipo de correlación lineal hay entre las variables `happiness_rank` y `suicides_rank`?<jupyter_code>hap_sui.loc[:, ["happiness_rank", "suicides_rank"]].## FIX ME ##<jupyter_output><empty_output><jupyter_text>__Respuesta:__   ¿Qué tipo de correlación lineal hay entre las variables `happiness_rank` y `suicides_rank` por cada año?<jupyter_code>## FIX ME ##<jupyter_output><empty_output><jupyter_text>__Respuesta:__   ¿La respuesta de las dos preguntas anteriores cambia si se utilizan las variables `happiness_score` y `suicides_ratio_100k`?<jupyter_code>## FIX ME ##
## FIX ME ##<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07976110201329352, 'fraction_numerical': 0.012667373085444562, 'mean_word_length': 5.539527559055118, 'pattern_counts': {'":': 1, '<': 174, '<?xml version=': 0, '>': 175, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '43', 'dir': 'jupyter-structured-clean-dedup', 'id': '338619', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 7009, 'n_tokens_neox': 6705, 'n_words': 2543, 'path': 'Clases/m02_data_analysis/m02_homework/m02_homework.ipynb', 'repo_name': 'BOlguin/MAT281_portfolio', 'size': '20778'}