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预测值应显著上调。
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密度特征及目标变量(实际送达时长),便于后续模型训练与回溯分析。