4. 特征工程(时空特征):提取出发时间特征、路径距离与路况等级、地理围栏与POI密度

在ETA预测模型中,原始数据(如订单时间、GPS轨迹点)无法直接用于回归或树模型。特征工程的目标是将这些原始信息转化为模型可理解的数值化、结构化特征。本章聚焦于三大核心时空特征组:时间特征、路径特征与地理上下文特征。

4.1 出发时间特征:小时、星期与节假日

出发时间直接影响交通流量的周期性波动。我们提取以下三个维度的特征:

特征名称 数据类型 提取方法 典型值/示例
出发小时 int (0-23) 从时间戳中提取 hour 字段 8, 17
出发星期 int (0-6) 从时间戳中提取 weekday 字段 0(周一), 6(周日)
是否工作日 binary (0/1) 星期1-5且非节假日为1 1
节假日标签 int (0/1/2) 0=普通日, 1=法定节假日, 2=调休日 1
高峰时段标志 binary (0/1) 早高峰7-9点或晚高峰17-19点为1 1
工程建议:小时和星期属于循环特征,建议使用正弦/余弦编码(sin/cos)将0-23和0-6映射到连续空间,避免模型误解“23点与0点”的距离为23。

4.2 路径距离与路况等级

路径距离是ETA的基础线性因子,而路况等级则反映非线性的拥堵延迟。我们构建以下特征:

  • 规划距离(km):基于高德/百度地图API返回的最短路径距离,直接作为数值特征。
  • 路况等级(0-4):将实时路况编码为有序分类特征:
    • 0 = 畅通(平均速度 > 40 km/h)
    • 1 = 缓行(30-40 km/h)
    • 2 = 拥堵(20-30 km/h)
    • 3 = 严重拥堵(10-20 km/h)
    • 4 = 瘫痪(< 10 km/h)
  • 红绿灯数量:路径途径的红绿灯路口总数,影响停车等待时间。
  • 路径曲折系数:规划距离 / 起终点直线距离,反映绕路程度。
特征交叉示例:“高峰时段 × 路况等级”交叉特征能显著提升模型对通勤时段拥堵的识别能力。例如,在早高峰(7-9点)且路况等级≥2时,ETA预测值应显著上调。
路径距离与路况等级特征提取流程 原始GPS轨迹点 地图API路径规划 规划距离 (km) 路况等级 (0-4) 红绿灯数量 特征向量拼接 → 输入ETA模型

4.3 地理围栏与POI密度特征

地理围栏(Geo-fence)用于识别起终点是否位于特殊区域(如商圈、学校、工业区),POI密度则量化区域的功能聚集程度。这些特征帮助模型理解“为什么相同距离下某些区域耗时更长”。

4.3.1 地理围栏编码

我们预先在城市地图上划定若干类围栏区域,并为每个起终点生成二进制标志:

  • 是否在核心商圈(如CBD、步行街)
  • 是否在学校/医院周边(上下学/就诊高峰)
  • 是否在交通枢纽(火车站、机场、长途客运站)
  • 是否在工业/物流园区(货车限行或集中通行区域)

4.3.2 POI密度特征

以起终点为中心,统计半径500米内的各类POI数量:

POI类别 特征名称 对ETA的影响方向
餐饮/购物 poi_commercial_density 正相关(停车难、人流量大)
住宅小区 poi_residential_density 正相关(早晚高峰出入车流)
公司/写字楼 poi_office_density 正相关(通勤潮汐)
停车场 poi_parking_density 负相关(找车位时间减少)
公交/地铁站 poi_transit_density 正相关(人车交织)
数据稀疏性注意:POI密度特征在不同城市等级间差异巨大(一线城市 vs 三线城市)。建议在模型训练时加入城市等级作为分组特征,或对POI密度进行城市内归一化。

4.4 特征工程代码实现(Python片段)

以下代码展示如何将原始订单数据转化为上述时空特征:

import pandas as pd
import numpy as np
from datetime import datetime

def extract_time_features(df, time_col='departure_time'):
    """提取出发时间特征"""
    df['departure_hour'] = df[time_col].dt.hour
    df['departure_weekday'] = df[time_col].dt.weekday
    df['is_weekend'] = (df['departure_weekday'] >= 5).astype(int)
    # 高峰时段标志
    df['is_peak_hour'] = ((df['departure_hour'] >= 7) & (df['departure_hour'] <= 9) |
                          (df['departure_hour'] >= 17) & (df['departure_hour'] <= 19)).astype(int)
    # 循环特征编码
    df['hour_sin'] = np.sin(2 * np.pi * df['departure_hour'] / 24)
    df['hour_cos'] = np.cos(2 * np.pi * df['departure_hour'] / 24)
    return df

def extract_geo_features(df, geo_api_func):
    """调用地图API获取路径距离与路况等级"""
    df['route_distance_km'] = df.apply(lambda row: geo_api_func(
        row['start_lng'], row['start_lat'],
        row['end_lng'], row['end_lat']
    )['distance'], axis=1)
    df['traffic_level'] = df.apply(lambda row: geo_api_func(
        row['start_lng'], row['start_lat'],
        row['end_lng'], row['end_lat']
    )['traffic_rating'], axis=1)
    return df

def extract_poi_density(df, poi_db, radius=500):
    """基于POI数据库计算密度特征"""
    poi_types = ['commercial', 'residential', 'office', 'parking', 'transit']
    for poi_type in poi_types:
        df[f'poi_{poi_type}_density'] = df.apply(
            lambda row: poi_db.query_count(
                row['start_lng'], row['start_lat'], poi_type, radius
            ), axis=1
        )
    return df
特征存储建议:将提取好的特征持久化为Parquet格式,按日期分区存储。每行记录包含订单ID、时间特征、路径特征、POI密度特征及目标变量(实际送达时长),便于后续模型训练与回溯分析。