Análise exploratória de dados (EDA)

Antes de construir recursos ou modelos de previsão de treinamento, devemos explorar o conjunto de dados para entender suas características, descobrir padrões de demanda horária / diária e detectar anomalias.

Neste caderno, analisamos os históricos logs de coleta NYC Yellow Taxi agrupados por hora e zona de localização.

Cell 02Code
import matplotlib.pyplot as plt
import pandas as pd

# Load the preprocessed dataset
dataset_path = "../data/processed/training_dataset.parquet"
df = pd.read_parquet(dataset_path)

print(f"Dataset shape: {df.shape}")
print("Columns:", list(df.columns))
df.head()
Stream
Dataset shape: (71253, 8)
Columns: ['pickup_location_id', 'pickup_hour', 'pickup_count', 'hour', 'day_of_week', 'is_weekend', 'month', 'next_hour_pickup_count']
pickup_location_id pickup_hour pickup_count hour day_of_week is_weekend month next_hour_pickup_count
0 1 2022-10-24 17:00:00 1 17 0 False 10 1
1 1 2022-10-25 03:00:00 1 3 1 False 10 1
2 1 2023-01-01 05:00:00 1 5 6 True 1 1
3 1 2023-01-01 08:00:00 1 8 6 True 1 1
4 1 2023-01-01 11:00:00 1 11 6 True 1 3

Sumário Estatísticas

Vejamos as estatísticas básicas de nossos dados de demanda, especialmente a coluna de destino next_hour_pickup_count.

Cell 04Code
df["next_hour_pickup_count"].describe()
Result
count    71253.000000
mean        42.985530
std         73.051339
min          1.000000
25%          1.000000
50%          7.000000
75%         55.000000
max        699.000000
Name: next_hour_pickup_count, dtype: float64

Distribuição Variável Alvo e Características Estatísticas

Vamos visualizar a distribuição da demanda de coleta de próxima hora em todas as zonas. A demanda de táxi amarelo normalmente exibe duas características estatísticas principais:

  1. Sparsity & Inflação Zero: Uma proporção significativa de horas em zonas residenciais ou remotas tem exatamente 0 embarques.
  2. Sobredispersão: A variância da contagem de captação é muito maior do que sua média (σ2≫μ\sigma^2 \gg \mu). Modelos de contagem padrão como regressão de Poisson assumem média variância; uma vez que isso é violado, modelos lineares L2-regularizados são preferidos para evitar overfitting sobre o ruído de zonas de alta variação.
Cell 06Code
mean_demand = df["next_hour_pickup_count"].mean()
var_demand = df["next_hour_pickup_count"].var()

plt.figure(figsize=(10, 5))
plt.hist(
    df["next_hour_pickup_count"],
    bins=50,
    color="#1f77b4",
    edgecolor="black",
    alpha=0.7,
)
plt.axvline(
    mean_demand,
    color="red",
    linestyle="dashed",
    linewidth=2,
    label=f"Mean: {mean_demand:.2f}",
)
plt.title("Distribution of Next-Hour Taxi Demand", fontsize=14)
plt.xlabel("Demand (Ride Count)", fontsize=12)
plt.ylabel("Frequency", fontsize=12)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.show()

print(f"Target Mean (mu): {mean_demand:.2f}")
print(f"Target Variance (sigma^2): {var_demand:.2f}")
print(f"Overdispersion Ratio (sigma^2 / mu): {var_demand / mean_demand:.2f}")
Notebook output image
ChartNotebook output image
Stream
Target Mean (mu): 42.99
Target Variance (sigma^2): 5336.50
Overdispersion Ratio (sigma^2 / mu): 124.15

Perfis horários e diários

Vamos inspecionar como a demanda varia por hora do dia e dia da semana. A demanda de táxi amarelo normalmente tem fortes padrões diurnos e aumenta durante o horário de pico da noite.

Cell 08Code
# Extract hour and day of week
df["pickup_hour"] = pd.to_datetime(df["pickup_hour"])
df["hour"] = df["pickup_hour"].dt.hour
df["day_of_week"] = df["pickup_hour"].dt.dayofweek

# Average pickups by hour
hourly_demand = df.groupby("hour")["next_hour_pickup_count"].mean()
plt.figure(figsize=(10, 5))
plt.plot(
    hourly_demand.index,
    hourly_demand.values,
    marker="o",
    color="#253494",
    linewidth=2,
)
plt.title("Average Next-Hour Demand by Hour of Day", fontsize=14, pad=15)
plt.xlabel("Hour of Day", fontsize=12)
plt.ylabel("Average Pickups", fontsize=12)
plt.xticks(range(24))
plt.grid(True, linestyle="--", alpha=0.5)
plt.show()

# Average pickups by day of week
weekly_demand = df.groupby("day_of_week")["next_hour_pickup_count"].mean()
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
plt.figure(figsize=(8, 4))
plt.bar(
    days, weekly_demand.values, color="#78c679", edgecolor="black", alpha=0.8
)
plt.title("Average Next-Hour Demand by Day of Week", fontsize=13, pad=12)
plt.xlabel("Day of Week", fontsize=11)
plt.ylabel("Average Pickups", fontsize=11)
plt.grid(axis="y", linestyle="--", alpha=0.5)
plt.show()
Notebook output image
ChartNotebook output image
Notebook output image
ChartNotebook output image

Análise de Autocorrelação Temporal

Para justificar o uso de lags históricos em nossos modelos e modelos Feature Store, analisamos o Função de Autocorrelação (ACF). O ACF mede a correlação da série de tempo de demanda consigo mesmo em diferentes lag temporais (em horas).

Cell 10Code
# Select the most active zone to see a clean seasonal signal
active_zone = df["pickup_location_id"].value_counts().index[0]
zone_df = df[df["pickup_location_id"] == active_zone].sort_values(
    "pickup_hour"
)
series = zone_df["next_hour_pickup_count"]

lags = 180
acf_vals = [series.autocorr(lag=i) for i in range(lags + 1)]

plt.figure(figsize=(12, 5))
plt.bar(
    range(lags + 1),
    acf_vals,
    color="#e6550d",
    width=0.8,
    edgecolor="none",
)

# Add dashed lines for key lags
plt.axvline(1, color="red", linestyle="--", alpha=0.7)
plt.axvline(24, color="blue", linestyle="--", alpha=0.7)
plt.axvline(168, color="purple", linestyle="--", alpha=0.7)

# Add annotations
plt.text(
    2,
    0.85,
    "Lag 1: Hourly momentum",
    color="red",
    fontsize=10,
    fontweight="bold",
)
plt.text(
    25,
    0.70,
    "Lag 24: Daily cycle",
    color="blue",
    fontsize=10,
    fontweight="bold",
)
plt.text(
    100,
    0.55,
    "Lag 168: Weekly cycle (same hour last week)",
    color="purple",
    fontsize=10,
    fontweight="bold",
)

plt.title(
    "Autocorrelation Function (ACF) - Location ID " + str(active_zone),
    fontsize=14,
)
plt.xlabel("Lag (Hours)", fontsize=12)
plt.ylabel("Autocorrelation", fontsize=12)
plt.grid(True, alpha=0.3)
plt.show()
Notebook output image
ChartNotebook output image