Curso
Nota del editor: Jean-Nicholas Hould es científico de datos en Intel Security (Montreal) y enseña cómo iniciarse en data science en su blog.
El análisis exploratorio de datos (EDA) es un enfoque estadístico orientado a descubrir y resumir un conjunto de datos. En esta fase del proceso de ciencia de datos, quieres explorar la estructura del dataset, sus variables y cómo se relacionan entre sí.
En este artículo, te centrarás en un aspecto concreto del EDA: el perfilado de datos.
El perfilado de datos consiste en resumir tu dataset mediante estadísticas descriptivas. Para comprenderlo a fondo, conviene apoyarse en un abanico amplio de métricas. Los tipos de datos, los valores ausentes, la media, la mediana o la desviación estándar son solo algunos de los elementos que tendrás que recopilar al perfilar un conjunto de datos. El objetivo es entender bien tus datos para, después, poder consultarlos y visualizarlos de distintas maneras.
Sabe de dónde vienen tus datos
Antes de lanzarte a cualquier EDA, intenta saber todo lo posible sobre el origen de los datos que vas a analizar. Necesitas entender cómo se recogieron y cómo se han procesado. ¿Ha habido transformaciones previas que puedan afectar a tu análisis?
Deberías poder responder a estas preguntas sobre tu dataset:
- ¿Cómo se recogió?
- ¿Es una muestra?
- ¿Se muestreó correctamente?
- ¿Se ha transformado el dataset de alguna manera?
- ¿Se conocen problemas en el conjunto de datos?
Si no entiendes de dónde provienen los datos, te costará mucho sacar conclusiones con sentido. Además, corres el riesgo de cometer errores de análisis importantes.
Asimismo, asegúrate de que el dataset esté estructurado de forma estandarizada. El formato recomendado es la tercera forma normal, también conocida como tidy data. Un dataset “ordenado” tiene estas características:
- Cada variable ocupa una columna y contiene valores
- Cada observación ocupa una fila
- Cada tipo de unidad observacional forma una tabla
Respetar este formato acelerará tu análisis, ya que es compatible con multitud de herramientas y librerías.
Perfilado de datos
En este post, utilizarás un dataset de cervezas artesanas en lata del sitio CraftCans. Este conjunto solo incluye datos de cervezas enlatadas de cervecerías de los Estados Unidos. No está claro si el dataset recoge absolutamente todas las cervezas enlatadas elaboradas en EE. UU. o no. Para curarte en salud, lo consideraremos una muestra que puede contener sesgos.
Esta es la estructura de los datasets que vas a usar:
Beers:
ID: identificador único de la cerveza.Name: nombre de la cerveza.ABV: graduación alcohólica (alcohol por volumen).IBU: unidades internacionales de amargor.Style: estilo de la cerveza.Ounces: onzas de cerveza.
Breweries:
ID: identificador único de la cervecería.Name: nombre de la cervecería.City: ciudad en la que se ubica la cervecería.State: estado en el que se ubica la cervecería.
Tipos de datos
El primer paso es entender de qué está compuesto tu dataset.
¿Con qué variables estás trabajando?
Por lo general, puedes encajar los datos en una de estas categorías:
- Numéricos
- Categóricos
- Texto
- Fecha
Primero importarás los datasets que encontrarás en este repositorio con la función from_csv de pandas. También unirás los datasets de beers y breweries para facilitar el análisis más adelante.
import pandas as pd
beers = pd.DataFrame.from_csv("https://raw.githubusercontent.com/nickhould/craft-beers-dataset/master/data/craftcans_beers.csv")
breweries = pd.DataFrame.from_csv("https://raw.githubusercontent.com/nickhould/craft-beers-dataset/master/data/craftcans_breweries.csv")
beers_and_breweries = pd.merge(beers,
breweries,
how='inner',
left_on="brewery_id",
right_on="id",
sort=True,
suffixes=('_beer', '_brewery'))
Con la librería pandas, puedes ejecutar la función dtypes para listar cada columna y su tipo de dato.
beers.dtypes
Que te devuelve el siguiente resultado:
abv float64
ibu float64
id int64
name object
style object
brewery_id int64
ounces float64
dtype: object
Como ves, esta función no agrupa de forma limpia los tipos de datos. Los distintos tipos numéricos (float64 e int64) no se agrupan en una sola categoría, como nos gustaría. Además, algunas columnas aparecen como objetos, lo cual no ayuda demasiado.
Para solucionarlo, puedes crear tu propia función que determine la categoría de cada columna de un DataFrame.
def get_var_category(series):
unique_count = series.nunique(dropna=False)
total_count = len(series)
if pd.api.types.is_numeric_dtype(series):
return 'Numerical'
elif pd.api.types.is_datetime64_dtype(series):
return 'Date'
elif unique_count==total_count:
return 'Text (Unique)'
else:
return 'Categorical'
def print_categories(df):
for column_name in df.columns:
print(column_name, ": ", get_var_category(df[column_name]))
Beers variables
print_categories(beers)
El comando anterior te devuelve:
abv : Numerical
ibu : Numerical
id : Numerical
name : Categorical
style : Categorical
brewery_id : Numerical
ounces : Numerical
Breweries variables
print_categories(breweries)
Que te devuelve el siguiente resultado:
name : Categorical
city : Categorical
state : Categorical
id : Numerical
Con esta información, ya puedes entender mejor el dataset. Sabes que solo tienes datos categóricos y numéricos. Las variables numéricas permiten extraer muchas métricas diferentes, como la media, la desviación estándar, etc. Las variables categóricas suelen ser útiles para segmentar y agrupar la información. Por ejemplo, puede interesarte ver cómo varía el IBU entre los distintos estilos de cerveza.
Estadística descriptiva
En esta sección, recorrerás distintas estadísticas descriptivas para comprender mejor los datos. Verás que cada métrica por separado aporta poco; es la combinación de varias donde realmente se extrae valor.
Nos centraremos en la variable IBU porque es numérica. Este tipo de variables ofrece un abanico más amplio de medidas que las categóricas. Aun así, también puedes calcular métricas sobre variables categóricas, aunque con más limitaciones.
Longitud
La función len cuenta el número de observaciones en una Series. Cuenta todas las observaciones, haya o no valores ausentes o nulos.
length = len(beers["ibu"])
print(length)
En esta Series tenemos un total de 2410 observaciones.
Recuento
La función count devuelve el número de observaciones no NA/no nulas en una Series.
count = beers["ibu"].count()
print(count)
Como ves, hay 1405 observaciones no nulas en la Series.
Valores ausentes
Con la Length y el Count, ya podemos calcular cuántos valores faltan. Es la diferencia entre la Length y el Count.
number_of_missing_values = length - count
pct_of_missing_values = float(number_of_missing_values / length)
pct_of_missing_values = "{0:.1f}%".format(pct_of_missing_values*100)
print(pct_of_missing_values)
Para expresarlo en porcentaje, divide el número de valores ausentes entre el total de observaciones (la longitud). La función float asegura que se conserven los decimales en la división. La función format formatea el número como un porcentaje.
En este caso, falta casi un 42% de la variable IBU. Es importante saberlo porque afectará a tu análisis. La mayoría de estadísticas descriptivas ignorarán esos valores ausentes y eso puede introducir sesgo.
Valor mínimo/máximo
Puedes obtener el valor mínimo y máximo fácilmente con las funciones min y max sobre una Series.
print("Minimum value: ", beers["ibu"].min())
print("Maximum value: ", beers["ibu"].max())
Los valores mínimo y máximo ayudan a entender el rango de una variable. En este caso, el IBU va de 4 a 138.
Moda
La moda es el valor más frecuente en un conjunto de datos. Puedes obtenerla con la función mode sobre una Series.
print(beers["ibu"].mode())
En una distribución normal, la moda coincide con la mean y la median.
En nuestro caso, la moda de IBU es 20. Es el valor más frecuente del dataset.
Media
La media es una medida de tendencia central. Es la suma de los valores dividida entre el número de observaciones no ausentes.
Se obtiene con la función mean sobre una Series.
mean = beers["ibu"].mean()
La media es sensible a los valores atípicos: unos pocos extremos pueden desplazarla notablemente hacia arriba o abajo.
Mediana
La mediana también es una medida de tendencia central. Es el valor situado exactamente en el centro de una lista ordenada de valores numéricos.
median = beers["ibu"].median()
En distribuciones sesgadas, la mediana suele ser una medida mucho más representativa que la media.
En la distribución de IBU, la media y la mediana están en el mismo orden de magnitud.
Desviación estándar
La desviación estándar es una medida de dispersión. Un valor alto indica que los datos están muy dispersos. Se expresa en la misma unidad que los valores.
standarddev = beers["ibu"].std()
En este caso, la desviación estándar es ~26 (25.954065911259324, con exactitud). Si la distribución de IBU fuese normal, ~68% de las observaciones estarían a una desviación estándar de la media.
Estadística de cuantiles
Los cuantiles son puntos de corte que dividen una distribución en partes iguales. Muchos cuantiles tienen nombre propio. Si divides una distribución en cuatro grupos iguales, el cuantil se denomina quartile. Puedes crear cuantiles fácilmente con la función quantile sobre una Series. Esta función recibe una lista con los cuantiles a calcular. Abajo dividimos la distribución en cuatro grupos iguales.
quantile = beers["ibu"].quantile([.25, .5, .75])
| 0.25 | 21.0 |
| 0.50 | 35.0 |
| 0.75 | 64.0 |
| Name: ibu, | dtype: float64 |
Como ves, el cuantil 50% equivale a la mediana: es el valor que parte el dataset por la mitad. También puedes observar que el 75% de las observaciones son iguales o inferiores a 64 IBU. Además, el 50% de la distribución se sitúa entre 21 y 64 IBU. Ten en cuenta que estas métricas no consideran los valores ausentes.
Gráficos de distribución
Las visualizaciones son muy útiles en el análisis exploratorio. En este artículo no profundizaremos en el tema, pero no podemos hablar de perfilado sin mencionar la importancia de un gráfico de distribución de frecuencias. Es una de las visualizaciones más sencillas y potentes: muestra la frecuencia de cada valor en el dataset.
Para crearla, usamos la librería seaborn`` library with thedisplotfunction. This function expects aSeries` sin valores ausentes.
import seaborn as sns
sns.set(color_codes=True)
sns.set_palette(sns.color_palette("muted"))
sns.distplot(beers["ibu"].dropna());

En este gráfico de distribución se aprecian claramente algunos valores que ya calculamos. El valor mínimo está cerca de 0 IBU y el máximo, cerca de 140 IBU. También se ve que el valor más frecuente ronda los 20 IBU. Además, aparece un pico próximo a 60 IBU.
¿Por qué hay dos picos en esta distribución?
¿Qué lo explica? Es algo que podemos explorar en una segunda fase del EDA.
Correlaciones
Las correlaciones son una gran forma de descubrir relaciones entre variables numéricas. Hay varias maneras de calcularlas. El coeficiente de correlación de Pearson es un enfoque muy utilizado que mide la dependencia lineal entre dos variables. El coeficiente va de -1 a 1: 1 indica correlación positiva total, -1 negativa total y 0 indica que no hay correlación lineal. Podemos calcularlo con la función corr sobre un Series. Por defecto, usa Pearson, aunque permite otros métodos.
beers[["abv", "ibu", "ounces"]].corr()
| abv | ibu | ounces | |
| abv | 1 | 0.670621 | 0.172529 |
| ibu | 0.670621 | 1 | 0.054691 |
| ounces | 0.172529 | 0.054691 | 1 |
Como es lógico, la correlación de IBU consigo mismo es 1. Más interesante: la correlación entre ABV e IBU es 0.670621. No es una correlación perfecta, pero sí alta. Es un aspecto interesante para investigar más adelante.
Algunas notas sobre variables no numéricas
Las métricas anteriores aplican sobre todo a valores numéricos. Si trabajas con otros tipos de datos, como los categóricos, aún puedes extraer información útil. Por ejemplo, calcular la frecuencia de cada valor en el dataset.
Los DataFrame tienen una función llamada describe que resume el conjunto de datos. Si tu DataFrame solo contiene valores categóricos o de texto, el resumen se adapta específicamente a ese tipo de datos.
beers[["name", "style"]].describe()
| name | style | |
| count | 2410 | 2405 |
| unique | 2305 | 99 |
| top | Nonstop Hef Hop | American IPA |
| freq | 12 | 424 |
Librerías para perfilado
Como has visto, recopilar estadísticas descriptivas puede ser laborioso. Por suerte, existen librerías que hacen todo el trabajo por ti y generan un perfil claro de tus datos. pandas-profiling es una de ellas. Ofrece un perfilado estadístico listo para usar. Como nuestro dataset está ordenado y estandarizado, podemos aplicarla directamente.
import pandas_profiling
pandas_profiling.ProfileReport(beers_and_breweries)
Más preguntas
Por lo general, una vez has perfilado tu dataset, surgen más preguntas que al principio. Y eso es positivo: esas preguntas alimentarán tu EDA.
Aquí tienes algunas que nos han surgido durante el perfilado:
- El 41,7% de los valores de
IBUfaltan. ¿Por qué? ¿Cómo puede afectar al análisis? - Hay dos picos en la distribución de
IBU. ¿Qué lo explica? - ¿Qué explica la correlación entre
IBUyABV? ¿Qué influencia tiene el estilo de cerveza en esa correlación? - ¿Existen diferencias en
IBU,ABVoStyleentre regiones geográficas? ¿Y entre la Costa Este y la Costa Oeste?
El perfilado de datos no es un proceso lineal. A medida que filtres y segmentes tu dataset, volverás sobre él para calcular estadísticas descriptivas en subgrupos.
Próximos pasos
En este artículo has visto cómo perfilar un dataset. Ya sabes cómo asignar variables a grupos de tipos de datos específicos. También has calculado distintas estadísticas descriptivas y sabes interpretarlas. Por último, has visto que hay librerías que te ahorran el trabajo de cálculo para perfilar tus datos. Y, lo más importante, has generado más preguntas que nunca para impulsar tu análisis exploratorio.
Echa un vistazo al tutorial de análisis exploratorio de datos en Python de DataCamp.



