<jupyter_start><jupyter_text>Análisis Exploratorio **¿Qué es el análisis exploratorio de datos?**El análisis exploratorio de datos (*EDA, Exploratory Data Analysis*) es entender los datos resumiendo sus principales características, generalmente utilizando algún tipo de representación gráfica. Este es un paso muy importante para llegar a la modelización de los datos que luego se puede utilizar para aplicar Machine learning para algún tipo de análisis predictivo. Explorar los datos no es tarea sencilla, suele llevar una cantidad de tiempo considerable. A través del análisis exploratorio podemos definir claramente el problema. **¿Cómo realizamos el análisis exploratorio?**Bueno, esta es LA pregunta. Y lamentablemente como suele suceder en estos casos la respuesta es: Depende. Básicamente porque depende del conjunto de datos que se está analizando. No existe una receta que se aplique para todos los casos. La idea fundamental es aplicar diferentes herramientas y estrategias que permitan tener una mejor idea. Puede ser un proceso iterativo en donde cada ciclo hace que uno tenga mejor entendimiento. **¿Qué datos vamos a explorar hoy?**Hoy vamos a analizar datos sobre autos. El dataset tiene más de 10k registros con más de 10 columnas que detallan características de los autos como por ejemplo: el tipo de combustible que usan, la potencia del motor, la caja de trasmisión que usan, el consumo en ciudad y en ruta y mucho más. En este ejercicio vamos a explorar los datos y dejarlos listos para modelarlos.  1. Importamos los paquetes necesarios A continuación tenemos los paquetes que vamos a utilizar en este ejercicio. Esto lo hacemos siempre al principio.<jupyter_code>import pandas as pd
import numpy as np
import seaborn as sns                       #visualisation
import matplotlib.pyplot as plt             #visualisation
%matplotlib inline<jupyter_output><empty_output><jupyter_text>2. Cargamos la "data" en un "dataframe" El dataset viene en un formato de archivo que se llama CSV (*comma separated values*). Es un archivo de texto plano donde cada registro se pone en una línea del mismo y cada columna es separada por comas. Es un formato muy simple que se utiliza muchísimo para el intercambio de datos. Cargar los datos en un *dataframe* es uno de los pasos más importantes en el análisis exploratorio. Pandas tiene un método que nos permite leer un archivo CSV y cargarlo directamente en un dataframe. > **Google Colab**: para cargar el archivo CSV en Colab a la izquierda del notebook vas a ver un ícono de una carpeta. Si hacés click sobre esa carpeta te da la opción de subir el archivo. También podés ya tenerlo cargado en el Drive.  ![upload_csv_colab.png](attachment:upload_csv_colab.png) Para leer el archivo tenemos que usar el método `read_csv` que viene con Pandas. Como argumento le tenemos que pasar el nombre del CSV, en este caso: `car-data.csv`. Como a Pandas lo importamos con el nombre `pd` tenemos que usar la siguiente sintaxis:<jupyter_code>df = pd.read_csv("car-data.csv")<jupyter_output><empty_output><jupyter_text>Si Python no nos tiró ningún error, entonces ahora en la variable `df` tenemos un *dataframe* que tiene los datos que se leyeron del CSV.Para ver los primeros 5 registros usamos el método `head` (cabeza):<jupyter_code>df.head(5)<jupyter_output><empty_output><jupyter_text>Si queremos ver los últimos 5 registros, usamos `tail` (cola):<jupyter_code>df.tail(5)<jupyter_output><empty_output><jupyter_text>Y también podemos usar la función `len` para que nos diga cuántos registros tiene este *dataframe*:<jupyter_code>len(df)<jupyter_output><empty_output><jupyter_text>O también podemos usar el atributo `shape` del *dataframe* que nos va a devolver una tupla con la cantidad de registros y la cantidad de columnas por registro:<jupyter_code>df.shape<jupyter_output><empty_output><jupyter_text>Y por último podemos usar el atributo `describe` del *dataframe* para conocer los estadísticos descriptivos básicos del dataframe como ser:* count: Cantidad de valores no nulos* mean: Media* std: Desviación estandar* min/25%/50%/75%/max: Valores mínimo, de percentiles 25, 50 y 75 y máximo.<jupyter_code>df.describe()<jupyter_output><empty_output><jupyter_text>3. Revisando los tipos de datos de cada columna Este paso es importante porque muchas veces por ejemplo, el precio o algún otro dato puede venir como una cadena de texto cuando en realidad queremos que sea un número de algún tipo. Si éste fuera el caso, tenemos que hacer la transformación del tipo de dato en la columna. En este caso en particular, por suerte, los datos vinieron correctamente.<jupyter_code>df.dtypes<jupyter_output><empty_output><jupyter_text>4. Eliminando columnas Este paso es necesario en cada análisis exploratorio que vayas a realizar para evitar tener columnas en tu *dataframe* que no vayan a aportarte información en tu análisis. En este caso, a modo de ejemplo, podemos eliminar las columnas: Engine Fuel Type, Market Category, Vehicle style, Popularity, Number of doors, Vehicle Size.<jupyter_code>df = df.drop(['Engine Fuel Type', 'Market Category', 'Vehicle Style', 'Popularity', 'Number of Doors', 'Vehicle Size'], axis=1)
df.head(5)<jupyter_output><empty_output><jupyter_text>5. Renombrando las columnas En esta etapa lo que hacemos es ponerle un nombre que nos resulte más descriptivo. Muchas veces los datos vienen con nombres en las columnas un tanto confusos y siempre es mejor "explícito". Es un buen ejercicio que **mejora la legibilidad**.<jupyter_code>df = df.rename(columns={"Engine HP": "HP", "Engine Cylinders": "Cilindros", "Transmission Type": "Transmision", "Driven_Wheels": "Tracción","highway MPG": "MPG-H", "city mpg": "MPG-C", "MSRP": "Precio" })
df.head(5)<jupyter_output><empty_output><jupyter_text>6. Eliminando registros duplicados Es altamente probable que en un dataset de +10k registros algunos de ellos se encuentren duplicados. Tener registros duplicados en nuestro análisis puede alterar los resultados y por ende, nuestras conclusiones y el modelo que proponemos.Con `shape` vemos que actualmente el *dataframe* tiene 11914 registros con 10 columas.<jupyter_code>df.shape<jupyter_output><empty_output><jupyter_text>Eliminar los duplicados con un *dataframe* de Pandas es tan complejo como llamar al método `drop_duplicates`. Este médoto nos devuelve un *nuevo dataframe*, si no queremos tener un nuevo *dataframe* lo que hacemos es simplemente darle el mismo nombre al nuevo:<jupyter_code>df = df.drop_duplicates()
df.shape<jupyter_output><empty_output><jupyter_text>Ahora el *dataframe* tiene 10925 registros, es decir que había 989 registros duplicados.<jupyter_code>11914 - 10925<jupyter_output><empty_output><jupyter_text>7. Eliminando registros con valores incompletos Los *dataframes* de Pandas tienen un método `count` que nos informa por columna cuántos registros tienen valores:<jupyter_code>df.count()<jupyter_output><empty_output><jupyter_text>Este paso es muy similar al anterior, sólo que esta vez vamos a eliminar los registros que no tengan valores.Es común en vez de eliminar estos registros, reemplazar los valores faltantes por el valor promedio. En este ejemplo, los valores faltantes están en el orden de las decenas mientras que tenemos un total de 10k registros. Es decir, el porcentaje es muy bajo y por eso optamos por descartar esos valores.<jupyter_code>10925 - 10856
10925 - 10895<jupyter_output><empty_output><jupyter_text>Notemos que en la columna `HP` tenemos 10856 registros con datos sobre un total de 10925. Es decir, hay 69 registros que no tienen un valor.Y en la columna `Cilindros` tenemos 10895 registros con datos. Es decir, hay 30 registros que no tienen un valor. También podemos comprobarlo preguntándole al *dataframe* que nos diga si hay datos vacíos o en *null* y pidiéndole que nos haga la suma de esos:<jupyter_code>df.isnull().sum()<jupyter_output><empty_output><jupyter_text>Para borrar esos registros que no tienen datos, registros que tienen "datos no disponibles" (del inglés *not available*) usamos el método `dropna`.<jupyter_code>df = df.dropna()
df.count()<jupyter_output><empty_output><jupyter_text>Ahora nuestro dataset sabemos que no tiene registros duplicados y que todos nuestros registros tienen valores en las 10 columnas.<jupyter_code>df.isnull().sum()<jupyter_output><empty_output><jupyter_text>---  8. Aplicando transformaciones El origen de estos datos utiliza el sistema inglés. Por eso tenemos MPG, millas por galón. No sé ustedes, pero yo prefiero utilizar el sistema internacional. Sabiendo que 1 MPG equivale a 0.425144 KPL (Kilómetros por litro) podemos transformar esas columnas para hacer una mejor interpretación.Si llamamos al *dataframe* con el nombre de una columna podemos operar con esa columna:<jupyter_code>df["MPG-H"]
df["MPG-H"] = df["MPG-H"] * 0.425144
df["MPG-H"]<jupyter_output><empty_output><jupyter_text>Lo mismo hacemos para la columna `MPG-C`:<jupyter_code>df["MPG-C"] = df["MPG-C"] * 0.425144
df["MPG-C"]<jupyter_output><empty_output><jupyter_text>Y ahora deberíamos cambiar el nombre, porque ese nombre ya no es significativo.<jupyter_code>df = df.rename(columns={"MPG-H": "KPL-R", "MPG-C": "KPL-C"})
df.head(5)<jupyter_output><empty_output><jupyter_text>9. Detectando valores atípicos Un valor atípico es un punto o un set de puntos que son diferentes de otros en el dataset. A veces puede ser valores que están muy por debajo o muy por encima. En general suele ser una buena medida quitar estos valores que no resultan representativos. Estos valores atípicos suelen generar modelos que no resultan representativos. Es por ello que es una buena alternativa removerlos del dataset.  Abajo vemos cómo con Boxplot (o diagrama de cajas) podemos observar los valores atípicos rápidamente. Si quieren más info sobre cómo detectar valores atípicos pueden ver el siguiente artículo de [towards data science](https://towardsdatascience.com/ways-to-detect-and-remove-the-outliers-404d16608dba).<jupyter_code>sns.boxplot(data=df)<jupyter_output><empty_output><jupyter_text>Analicemos por separado para evitar el sesgo de Precio<jupyter_code>sns.boxplot(x=df['Precio'])
sns.boxplot(x=df['HP'])
sns.boxplot(x=df['Cilindros'])<jupyter_output><empty_output><jupyter_text>Analicemos la disperción del precio que como se ve, tiene valores outliers:<jupyter_code>sns.distplot(df['Precio'])
df['Precio'].describe()<jupyter_output><empty_output><jupyter_text>Se observa que Precio tiene una larga cola hacia la derecha.Analicemos los deciles y algunos percentiles para entender esos valores anómalos. Para esto, usaremos el método quantile para calcular los percentiles.* Los cuartiles, que dividen a la distribución en cuatro partes (corresponden a los cuantiles 0,25; 0,50 y 0,75);* Los quintiles, que dividen a la distribución en cinco partes (corresponden a los cuantiles 0,20; 0,40; 0,60 y 0,80);* Los deciles, que dividen a la distribución en diez partes;* Los percentiles, que dividen a la distribución en cien partes.* En el método Quantile: 0, 0.5 y 1 equivalen a valores mínimo, mediana y máximo.<jupyter_code>sns.boxplot(x=df['Precio'])
df['Precio'].quantile([0, .1, .2, .3, .4, .5, .6, .7, .8, .9, .97, .98, .99, 1])<jupyter_output><empty_output><jupyter_text>Recortemos los outliers... por ejemplo, recortando entre 200 y el cuartil del 90% (67208 como vimos en la celda anterior)<jupyter_code>corte_inferior = 200
corte_superior = df["Precio"].quantile(0.9)

df = df[(df["Precio"] < corte_superior) & (df["Precio"] > corte_inferior)]<jupyter_output><empty_output><jupyter_text>Veamos cómo quedó la distribución después de eliminar los outliers:<jupyter_code>sns.boxplot(x=df['Precio'])
sns.distplot(df['Precio'])<jupyter_output><empty_output><jupyter_text>¿Cómo definimos el corte inferior? Mejor preguntemosle al área de negocio que nos pasó este set de datos.  10. Gráficas  HistogramaEl histograma es una gráfica que representa la cantidad de ocurrencias de una variable en un intervalo determinado. En este dataset tenemos principalmente 10 diferentes marcas de fabricantes, pero es importante saber saber quién tiene la mayor cantidad de autos. Este histograma es una solución trivial para saber cuántos autos fabrica cada compañía.<jupyter_code>df["Make"].value_counts().plot(kind='bar', figsize=(20,5))
plt.title("Cantidad de autos por marca")
plt.ylabel('Cantidad de autos')
plt.xlabel('Marca');<jupyter_output><empty_output><jupyter_text>Mapas de calorLos mapas de calor son gráficas que sirven para determinar variables dependientes. Una de las mejores maneras para encontrar estas relaciones es utilizando un mapa de calor. En el siguiente mapa de calor encontramos que el precio es una característica dependiente del tamaño del motor, de la potencia y de la cantidad de cilindros.<jupyter_code>correlaciones= df.corr()
sns.heatmap(correlaciones)
correlaciones<jupyter_output><empty_output><jupyter_text>DispersiónUsamos este tipo de gráficas para encontrar correlación entre 2 variables. Acá graficamos precio en función de la potencia.We generally use scatter plots to find the correlation between two variables. Here the scatter plots are plotted between Horsepower and Price and we can see the plot below. With the plot given below, we can easily draw a trend line. These features provide a good scattering of points.<jupyter_code>fig, ax = plt.subplots(figsize=(10,6))
ax.scatter(df['HP'], df['Precio'])
ax.set_xlabel('HP')
ax.set_ylabel('Precio')
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07415930517407765, 'fraction_numerical': 0.014920941281270878, 'mean_word_length': 5.955085183273103, 'pattern_counts': {'":': 9, '<': 122, '<?xml version=': 0, '>': 123, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '30', 'dir': 'jupyter-structured-clean-dedup', 'id': '633529', 'license': 'CC0-1.0', 'n_tokens_mistral': 4677, 'n_tokens_neox': 4432, 'n_words': 1677, 'path': 'analisis_exploratorio.ipynb', 'repo_name': 'lecovi/dataschool-py3', 'size': '13471'}