日期与时间特征提取
您将把 datetime 列分解为年份、月份、星期几和小时,并编码为周期性的正弦/余弦特征,以捕捉周期性。
日期与时间特征提取 是 CoddyKit 上的免费 Machine Learning Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Machine Learning Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Machine Learning Academy 课程共包含 4 节课。
原始时间戳为何毫无用处
原始 Unix 时间戳(如 1718784000)或日期时间字符串(如 '2024-06-19 14:30:00')包含丰富的信息——一天中的小时、星期几、一年中的月份等——但模型无法直接从原始值中获取这些信息。线性模型会将时间戳视为一个很大的整数,只能学习较晚的时间戳是否对应更高或更低的目标值,却会错过所有周期性模式。特征提取会将时间戳分解为有意义的数值成分,供模型直接使用。
使用 Pandas 解析日期时间列
在提取特征之前,请始终使用 pd.to_datetime() 将日期时间字符串解析为 pd.Timestamp 或 datetime64 dtype。解析完成后,Pandas 会提供一个 .dt 访问器,其中包含数十个属性:.dt.year、.dt.month、.dt.day、.dt.hour、.dt.minute、.dt.dayofweek(0=星期一)、.dt.dayofyear、.dt.quarter、.dt.is_weekend 等。每个属性都会成为特征矩阵中的一个新整数列。
import pandas as pd
dates = pd.Series(['2024-01-15 08:30:00', '2024-06-21 17:45:00', '2024-12-25 12:00:00'])
dts = pd.to_datetime(dates)
df = pd.DataFrame({
'year': dts.dt.year,
'month': dts.dt.month,
'day': dts.dt.day,
'hour': dts.dt.hour,
'dayofweek': dts.dt.dayofweek, # 0=Mon, 6=Sun
'quarter': dts.dt.quarter,
'is_weekend': (dts.dt.dayofweek >= 5).astype(int)
})
print(df.to_string())原始周期性特征的问题
第 23 小时和第 0 小时彼此相邻(仅相差一小时),但在数值上却相距很远(相差 23 个单位)。如果将小时编码为 0~23 的整数,线性模型就无法学习到午夜的模式与晚上 11 点的模式相似。同样的问题也适用于月份(12 月和 1 月彼此相邻)、星期几(星期日=6,星期一=0)以及其他周期性数值。将它们作为原始整数处理,会在周期边界处制造人为的不连续性,模型必须设法克服这种不连续。
使用正弦和余弦进行周期编码
解决方案是将周期性特征编码为正弦和余弦对。对于取值范围为 0 到 T-1 的变量,可以将其编码为:sin(2π × value / T) 和 cos(2π × value / T)。这种方法会将周期映射到二维空间中的一个圆上。现在,第 23 小时和第 0 小时成为圆上相邻的点,因此它们之间的欧氏距离很小。任何计算距离或线性组合的模型,都可以结合 sin 和 cos 成分正确捕捉周期性模式。
import numpy as np
import pandas as pd
hours = np.arange(24)
df = pd.DataFrame({'hour': hours})
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
# Distance between hour 23 and hour 0 on the circle
def circle_dist(h1, h2):
s1, c1 = df.loc[df['hour'] == h1, ['hour_sin', 'hour_cos']].values[0]
s2, c2 = df.loc[df['hour'] == h2, ['hour_sin', 'hour_cos']].values[0]
return np.sqrt((s1-s2)**2 + (c1-c2)**2)
print('Distance 23 to 0:', round(circle_dist(23, 0), 4)) # small
print('Distance 0 to 12:', round(circle_dist(0, 12), 4)) # large将周期编码应用于多个特征
周期编码模式可以推广到任何周期性特征:月份(T=12)、星期几(T=7)、小时(T=24)、分钟(T=60)、一年中的第几天(T=365)。每个周期性特征都会生成两个新特征(sin 和 cos)。对于基于树的模型,原始整数编码通常已经足够,因为树会按阈值进行划分,并可以通过多次划分捕捉周期性模式。但对于线性模型、神经网络和基于距离的算法,周期编码非常重要。
import numpy as np
import pandas as pd
def cyclical_encode(df, col, period):
df[col + '_sin'] = np.sin(2 * np.pi * df[col] / period)
df[col + '_cos'] = np.cos(2 * np.pi * df[col] / period)
return df
df = pd.DataFrame({'hour': [8, 12, 20, 23], 'month': [1, 6, 11, 12], 'dow': [0, 2, 4, 6]})
df = cyclical_encode(df, 'hour', 24)
df = cyclical_encode(df, 'month', 12)
df = cyclical_encode(df, 'dow', 7)
print(df[['hour', 'hour_sin', 'hour_cos', 'month', 'month_sin', 'month_cos']].round(3))距参考时间的时长:相对时间戳
有时重要的并不是提取日历成分,而是某个事件发生后经过的时间:距上次购买的天数、距系统重启的小时数、距账户创建的月数。这些相对时长特征可以捕捉绝对日历特征无法体现的新近性效应。您可以通过简单的差值计算它们:(now - event_date).dt.days 或 (now - event_date).dt.total_seconds()。新近性特征对于客户行为模型(流失、LTV)和预测性维护尤其有价值。
import pandas as pd
import numpy as np
df = pd.DataFrame({
'last_purchase': pd.to_datetime(['2024-01-10', '2024-05-20', '2024-06-15']),
'signup_date': pd.to_datetime(['2023-01-01', '2023-06-15', '2024-01-01'])
})
reference = pd.Timestamp('2024-06-19')
df['days_since_purchase'] = (reference - df['last_purchase']).dt.days
df['account_age_days'] = (reference - df['signup_date']).dt.days
print(df[['days_since_purchase', 'account_age_days']])周末和营业时间标记
从时间戳中派生的布尔指示特征虽然简单,却往往非常有效。is_weekend、is_business_hours、is_holiday、is_month_end 等特征会创建清晰的二元划分,树模型可以通过一次划分加以利用。线性模型也会受益,因为这些标记能够产生不同的截距调整。添加公共假日标记对于零售销售、交通流量和能源消耗模型尤其有影响,因为假日会导致需求发生系统性变化。
import pandas as pd
dates = pd.to_datetime(['2024-06-17', '2024-06-19', '2024-06-21', '2024-06-22', '2024-12-25'])
df = pd.DataFrame({'date': dates})
df['is_weekend'] = (df['date'].dt.dayofweek >= 5).astype(int)
df['is_month_end'] = df['date'].dt.is_month_end.astype(int)
df['is_quarter_end'] = df['date'].dt.is_quarter_end.astype(int)
df['week_of_year'] = df['date'].dt.isocalendar().week.astype(int)
print(df.to_string())滞后特征和滚动统计量
对于时间序列数据,目标变量的历史值往往是最好的预测变量之一。滞后特征会将目标值向后移动 N 个时间步:df['target_lag1'] = df['target'].shift(1) 会创建一个等于前一天目标值的特征。滚动统计量会概括近期历史:df['rolling_mean_7'] = df['target'].rolling(7).mean() 会得到 7 天移动平均值。这些特征能够编码仅凭日历特征无法捕捉的时间动量和季节性。
import pandas as pd
import numpy as np
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=10)
sales = np.array([100, 120, 95, 130, 110, 145, 160, 155, 170, 180])
df = pd.DataFrame({'date': dates, 'sales': sales})
df['lag_1'] = df['sales'].shift(1)
df['lag_7'] = df['sales'].shift(7)
df['rolling_mean_3'] = df['sales'].rolling(3).mean()
df['rolling_std_3'] = df['sales'].rolling(3).std()
print(df.to_string())在管道中组合日期特征
若要在 scikit-learn Pipeline 中使用日期特征提取而不发生数据泄漏,请使用 BaseEstimator 和 TransformerMixin 创建自定义变换器。这样可以确保在交叉验证的每个折中,以一致且可复现的方式计算日期特征。对于滞后特征和滚动特征,请格外谨慎:在完整数据集(包括未来的测试数据)上计算滚动统计量会造成信息泄漏。请始终只计算截至训练截止日期的数据所对应的滞后特征和滚动特征。
import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
class DateFeatureExtractor(BaseEstimator, TransformerMixin):
def fit(self, X, y=None):
return self
def transform(self, X):
X = X.copy()
dt = pd.to_datetime(X['date'])
X['hour'] = dt.dt.hour
X['dayofweek'] = dt.dt.dayofweek
X['month'] = dt.dt.month
X['hour_sin'] = np.sin(2 * np.pi * X['hour'] / 24)
X['hour_cos'] = np.cos(2 * np.pi * X['hour'] / 24)
X = X.drop('date', axis=1)
return X
df = pd.DataFrame({'date': ['2024-06-19 08:00', '2024-06-20 15:30'], 'value': [10, 20]})
print(DateFeatureExtractor().fit_transform(df))根据领域知识选择特征
有效的日期时间特征工程需要理解哪些时间模式与您的问题相关。对于网约车需求:一天中的小时和星期几至关重要。对于零售销售:星期几、一年中的第几周、与假日的接近程度以及同比趋势很重要。对于网络入侵检测:距上次事件的时间和突发速率很重要。对于能源消耗:小时、日期和温度之间的交互很重要。在设计特征之前,请始终先绘制目标值与各个时间成分之间的关系,以识别明显的周期性模式。
处理多个时区
当数据集跨越多个时区时,请始终在提取特征之前将时间戳转换到同一个参考时区。使用 UTC 作为标准时区是业内惯例。提取特征后,如果本地模式很重要,您可以重新应用本地时间偏移(例如,对于用户行为建模,客户当地时间的小时通常比 UTC 小时更具预测力)。Pandas 的 dt.tz_localize() 和 dt.tz_convert() 方法可以简洁地处理时区转换。
import pandas as pd
# Timestamps in mixed timezones
timestamps = ['2024-06-19 08:00:00-05:00', # US/Chicago
'2024-06-19 14:00:00+01:00', # Europe/London
'2024-06-19 22:00:00+09:00'] # Asia/Tokyo
df = pd.DataFrame({'ts': pd.to_datetime(timestamps, utc=True)})
df['utc_hour'] = df['ts'].dt.hour # all in UTC after conversion
df['ts_ny'] = df['ts'].dt.tz_convert('America/New_York')
df['ny_hour'] = df['ts_ny'].dt.hour # local time hour
print(df[['utc_hour', 'ny_hour']])快速检查
请通过本课内容检验您对日期和时间特征提取的理解。
课程回顾
本课您学到了:原始时间戳必须先分解为有意义的成分,才能用作特征,小时和月份等周期性特征需要使用正弦/余弦编码,以避免边界不连续,以及滞后特征和滚动特征可以为时间序列预测任务编码时间模式。接下来,我们将探索如何使用 VarianceThreshold 和 SelectKBest 进行特征选择,以剔除无信息的特征。
用 AI 导师学习 Python — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 30
- 课程
- 120
常见问题解答
「日期与时间特征提取」课时是免费的吗?
是的 — 「日期与时间特征提取」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Machine Learning Academy 课程的其余内容,请升级到 CoddyKit PRO。 Machine Learning Academy 课程共包含 4 节课。
「日期与时间特征提取」这节课中我会学到什么?
您将把 datetime 列分解为年份、月份、星期几和小时,并编码为周期性的正弦/余弦特征,以捕捉周期性。 你通过在浏览器中直接运行的动手代码来练习 Machine Learning Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Machine Learning Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Machine Learning Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「日期与时间特征提取」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Machine Learning Academy 课中编写并运行代码吗?
能。每节 Machine Learning Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。