Una potente herramienta EDA: agregación por grupos | por Pararawendy Indarjo | Jul, 2024
Foto por Mourizal Zativa en Dejar de salpicar

Aprenda a utilizar la agregación por grupos para descubrir información a partir de sus datos

El análisis exploratorio de datos (EDA) es la competencia principal de un analista de datos. Todos los días, los analistas de datos tienen la tarea de ver lo “invisible” o extraer información útil de un vasto océano de datos.

En este sentido, me gustaría compartir una técnica que considero beneficiosa para extraer información relevante de los datos: la agregación agrupada.

Para tal fin, el resto de este artículo se organizará de la siguiente manera:

  1. Explicación de la agregación por grupos en Pandas
  2. El conjunto de datos: Tráfico interestatal metropolitano
  3. EDA de tráfico metropolitano

La agregación por grupos es una técnica de manipulación de datos que consta de dos pasos. En primer lugar, agrupamos los datos en función de los valores de columnas específicas. En segundo lugar, realizamos algunas operaciones de agregación sobre los datos agrupados.

La agregación por grupos es especialmente útil cuando nuestros datos son granulares, como en el caso de las tablas de hechos típicas (datos de transacciones) y los datos de series temporales con intervalos estrechos. Al agregar a un nivel de granularidad más alto que el de los datos sin procesar, podemos representar los datos de una manera más compacta y extraer información útil en el proceso.

En pandas, podemos realizar agregaciones de grupo mediante la siguiente sintaxis general.

df.groupby(['base_col']).agg(
agg_col=('ori_col','agg_func')
)

Dónde base_col es la columna cuyos valores se convierten en la base de agrupación, agg_col ¿La nueva columna se define tomando agg_func agregación en ori_col columna.

Por ejemplo, consideremos el infame conjunto de datos del Titanic, cuyas cinco filas se muestran a continuación.

import pandas as pd
import seaborn as sns

# import titanic dataset
titanic = sns.load_dataset("titanic")
titanic.head()

Las primeras cinco filas de datos del Titanic (imagen del autor)

Podemos agrupar estos datos por survived columna y luego agregarla tomando la mediana de la fare columna para obtener los resultados a continuación.

Tarifa media de los pasajeros del Titanic, según su estado de supervivencia (imagen del autor)

De repente, vemos una idea interesante: los pasajeros que sobrevivieron tienen una tarifa media más alta, que se ha más que duplicado. Esto podría estar relacionado con la priorización de los barcos de seguridad para los pasajeros de cabinas superiores (es decir, pasajeros con billetes de tarifa más alta).

Con suerte, este sencillo ejemplo demuestra el potencial de la agrupación por agregación para obtener información a partir de los datos. Bien, ¡probemos la agrupación por agregación en un conjunto de datos más interesante!

Utilizaremos el conjunto de datos de volumen de tráfico interestatal del metro. Es un conjunto de datos disponible públicamente con una Licencia Creative Commons 4.0 (que permite compartir y adaptar el conjunto de datos para cualquier propósito).

El conjunto de datos contiene el volumen de tráfico por hora de Minneapolis a St. Paul, Minnesota, para la I-94 en dirección oeste, que también incluye detalles meteorológicos de 2012 a 2018. La información del diccionario de datos se puede encontrar en su Repositorio de aprendizaje automático de la UCI página.

import pandas as pd

# load dataset
df = pd.read_csv("dir/to/Metro_Interstate_Traffic_Volume.csv")

# convert date_time column from object to proper datetime format
df['date_time'] = pd.to_datetime(df['date_time'])

# head
df.head()

Datos de tráfico (df) cabecera (imagen del autor)

Para esta demostración del blog, solo utilizaremos datos a partir de 2016, ya que faltan datos de tráfico de períodos anteriores (¡intente comprobarlo usted mismo para hacer ejercicio!).

Además, agregaremos una nueva columna is_congestedque tendrá un valor de 1 si el traffic_volume excede 5000 y 0 en caso contrario.

# only consider 2016 onwards data
df = df.loc[df['date_time']>="2016-01-01",:]

# feature engineering is_congested column
df['is_congested'] = df['traffic_volume'].apply(lambda x: 1 if x > 5000 else 0)

Utilizando la agregación group-by como arma principal, intentaremos responder las siguientes preguntas de análisis.

  1. ¿Cómo es la progresión mensual del volumen de tráfico?
  2. ¿Cómo es el perfil del tráfico de cada día de la semana (lunes, martes, etc.)?
  3. ¿Cómo se desglosa el volumen de tráfico típico por hora durante 24 horas, entre semana y fin de semana?
  4. ¿Cuáles son las principales condiciones climáticas que corresponden a mayores tasas de congestión?

Evolución mensual del volumen de tráfico

Esta pregunta requiere que agreguemos (sumemos) los volúmenes de tráfico a nivel mensual. Porque no tenemos la month columna, necesitamos derivar una basada en date_time columna.

Con monthcolumna en su lugar, podemos agrupar en función de esta columna y tomar la suma de traffic_volumeLos códigos se dan a continuación.

# create month column based on date_time
# sample values: 2016-01, 2026-02
df['month'] = df['date_time'].dt.to_period("M")

# get sum of traffic_volume by month
monthly_traffic = df.groupby('month', as_index=False).agg(
total_traffic = ('traffic_volume', 'sum')
)

# convert month column to string for viz
monthly_traffic['month'] = monthly_traffic['month'].astype(str)

monthly_traffic.head()

tráfico mensual (imagen del autor)

¡Podemos dibujar un gráfico lineal a partir de este marco de datos!

# draw time series plot
plt.figure(figsize=(12,5))
sns.lineplot(data=monthly_traffic, x ="month", y="total_traffic")
plt.xticks(rotation=90)
plt.title("Monthly Traffic Volume")
plt.show()
Volumen de tráfico mensual (Imagen del autor)

La visualización anterior muestra que el volumen de tráfico generalmente ha aumentado a lo largo de los meses dentro del período de datos considerado.

Perfil de tráfico diario

Para analizar esto, necesitamos crear dos columnas adicionales: date y daynameEl primero se utiliza como base de agrupación principal, mientras que el segundo se utiliza como desglose al mostrar los datos.

En los siguientes códigos definimos date y dayname columnas. Luego, agrupamos en función de ambas columnas para obtener la suma de traffic_volume. Tenga en cuenta que desde dayname es más grueso (nivel de agregación más alto) que date significa efectivamente que agregamos en función de date valores.

# create column date from date_time
# sample values: 2016-01-01, 2016-01-02
df['date'] = df['date_time'].dt.to_period('D')

# create dayname column
# sample values: Monday, Tuesday
df['dayname'] = df['date_time'].dt.day_name()

# get sum of traffic, at date level
daily_traffic = df.groupby(['dayname','date'], as_index=False).agg(
total_traffic = ('traffic_volume', 'sum')
)

# map dayname to number for viz later
dayname_map = {
'Monday': 1,
'Tuesday': 2,
'Wednesday': 3,
'Thursday': 4,
'Friday': 5,
'Saturday': 6,
'Sunday': 7
}

daily_traffic['dayname_index'] = daily_traffic['dayname'].map(dayname_map)
daily_traffic = daily_traffic.sort_values(by='dayname_index')

daily_traffic.head()

Cabezal de tráfico diario (imagen del autor)

La tabla anterior contiene diferentes realizaciones del volumen total de tráfico diario por nombre de día. Las visualizaciones de diagramas de caja son adecuadas para mostrar esas variaciones del volumen de tráfico, lo que nos permite comprender cómo difieren los volúmenes de tráfico los lunes, martes, etc.

# draw boxplot per day name
plt.figure(figsize=(12,5))
sns.boxplot(data=daily_traffic, x="dayname", y="total_traffic")
plt.xticks(rotation=90)
plt.title("Daily Traffic Volume")
plt.show()

El gráfico anterior muestra que todos los días de la semana (de lunes a viernes) tienen aproximadamente la misma densidad de tráfico. Los fines de semana (sábado y domingo) tienen menos tráfico, siendo el domingo el que tiene menos tráfico.

Patrones de tráfico por hora, desglosados ​​por estado del fin de semana

De manera similar a las preguntas anteriores, necesitamos diseñar dos nuevas columnas para responder esta pregunta, es decir, hour y is_weekend.

Usando el mismo truco, agruparemos por is_weekend y hour columnas para obtener promedios de traffic_volume.

# extract hour digit from date_time
# sample values: 1,2,3
df['hour'] = df['date_time'].dt.hour

# create is_weekend flag based on dayname
df['is_weekend'] = df['dayname'].apply(lambda x: 1 if x in ['Saturday', 'Sunday'] else 0)

# get average traffic at hour level, broken down by is_weekend flag
hourly_traffic = df.groupby(['is_weekend','hour'], as_index=False).agg(
avg_traffic = ('traffic_volume', 'mean')
)

hourly_traffic.head()

Tráfico por hora (imagen del autor)

Para la visualización, podemos utilizar un gráfico de barras con desglose en is_weekend bandera.

# draw as barplot with hue = is_weekend
plt.figure(figsize=(20,6))
sns.barplot(data=hourly_traffic, x='hour', y='avg_traffic', hue='is_weekend')
plt.title("Average Hourly Traffic Volume: Weekdays (blue) vs Weekend (orange)", fontsize=14)
plt.show()
Patrón de tráfico por hora, según estado del fin de semana (imagen del autor)

¡Muy interesante y rica visualización! Observaciones:

  1. El tráfico entre semana tiene un patrón de distribución bimodal. Alcanza su máximo tráfico entre las 6 y las 8 de la mañana y entre las 16 y las 17 de la tarde. Esto es algo intuitivo porque esas ventanas de tiempo representan a las personas que van al trabajo y regresan a casa del trabajo.
  2. El tráfico de fin de semana sigue un patrón completamente diferente. Tiene una forma unimodal con una gran ventana de horas pico (12-17). A pesar de que en general es inferior (menor tráfico) a las horas equivalentes de los días laborables, cabe destacar que el tráfico de fin de semana es en realidad mayor durante las horas nocturnas (22-2). Esto podría deberse a que la gente se queda fuera hasta tarde los fines de semana por la noche.

Condiciones meteorológicas más frecuentes asociadas con la congestión

Para responder a esta pregunta, necesitamos calcular la tasa de congestión para cada condición climática en el conjunto de datos (utilizando is_congested columna). ¿Podemos calcularlo mediante agregación agrupada? ¡Sí podemos!

La observación clave que hay que hacer es que is_congested La columna es binaria. Por lo tanto, la tasa de congestión se puede calcular simplemente sacando el promedio de esta columna. El promedio de una columna binaria es igual a suma(valor 1)/conteo(todas las filas). Piense en esto por un momento si es nuevo para usted.

Con base en esta clara observación, todo lo que necesitamos hacer es tomar el promedio (media) de is_congested agrupado por weather_descriptionA continuación, ordenamos los resultados en orden descendente por congested_rate.

# rate of congestion (is_congested) , grouped by weather description
congested_weather = df.groupby('weather_description', as_index=False).agg(
congested_rate = ('is_congested', 'mean')
).sort_values(by='congested_rate', ascending=False, ignore_index=True)

congested_weather.head()

Cabeza de clima congestionado (imagen del autor)
# draw as barplot
plt.figure(figsize=(20,6))
sns.barplot(data=congested_weather, x='weather_description', y='congested_rate')
plt.xticks(rotation=90)
plt.title('Top Weather with High Congestion Rates')
plt.show()
El mejor clima según la tasa de congestión (imagen del autor)

Del gráfico:

  1. Las tres condiciones climáticas con mayores tasas de congestión son aguanieve, nevadas ligeras y lluvias muy intensas.
  2. Mientras tanto, la lluvia ligera y la nieve, las tormentas eléctricas con llovizna, la lluvia helada y los chubascos no han causado ningún atasco. ¡La gente debe quedarse en casa durante un clima tan extremo!