在进行时间序列机器学习模型的训练时,通常需要利用如下时间特征:小时、星期、月份以及周或年。将时间戳列转换为这些特征非常简单。确保将时间列转换为日期时间对象后,你可以使用.dt.Time
方法来提取大量的时间序列特征。
df['Hour']=df['Datetime'].dt.hour
df['Month']=df['Datetime'].dt.month
df['Dayofweek']=df['Datetime'].dt.dayofweek
举个例子,使用一个包含每小时电力消耗数据的数据集作为参考。能源消耗数据集通常属于时间序列数据,其最终目的是利用过去的数据来预测未来的消耗量,因此这是一个很好的应用案例。尽管温度、湿度和风速等外部特征也会对能源消耗产生影响,但在这里我会着重关注时间序列特征的提取和转换。
现在你已经从 0 个可用功能变成了 3 个。
在使用 ML 时,我们需要对特征进行适当的处理,不能直接将其原样传入模型。原因在于大多数模型会将时间序列特征错误地理解为数字特征。举例来说,在能源消耗方面,某些高峰时段通常会导致较高的能源消耗,而其他特定时段则有较低的能耗。换句话说,可以将每个小时视作一个类别。
通过放大数据集的特定部分,可以证明这一点。数据显示了明显的消费模式,例如在下午 5-6 点的使用量往往达到最高峰,而在上午 5-7 点的使用量则最低。
显然,时间/年份/月份和星期等特征之间存在着复杂的相互作用,因此我们需要将更多的信息纳入我们的模型中。
为了做到这一点,我们需要使用其他格式来编码分类特征,以确保模型能够正确理解这些特征。最常见的方法是使用独热编码。
One-Hot(独热编码)的实现非常简单直接。它的基本原理是,对于一天(或月、日等)中的任何给定小时,我们会询问“它是否是第n小时/日/月”?然后用一个二进制的0或1来回答。对每一种类别都是这样操作。因此,对于原始特征中的day_of_week
,将会得到7个编码特征(代表一周中的7天):
在 Python 中,最简单的方法是使用 pd.get_dummies
:
columns_to_encode = ['Hour', 'Month', 'Dayofweek']
df = pd.get_dummies(df, columns=columns_to_encode)
这将产生新的特征集
我们的特征非常丰富。我们已经将列数从原先的3列(小时、月、星期)增加到了40多列。随着需要编码的时间序列特征不断增加,这可能会变得越来越复杂。要跟踪所有这些特征可能会变得相当困难,特别是当您希望在数据库中存储或可视化这些特征时,您可能会希望避免产生过于混乱的图表。
时间序列数据具有周期性循环的特点。例如,一天被划分为24个小时,当时针指向24:00(凌晨 12 点),新的一天就开始了,之后是1点、2点...按顺序循环。尽管从数值上看,1点与24点(0点)相距最远,但实际上1点与23点一样接近0点,因为它们都处于同一个24小时周期内。
因此,除了用数值直接表示时间,我们还可以将时间戳转换为正弦和余弦值。这种方法实质上是将时间映射到单位圆上,根据时间在圆周上的位置,赋予对应的正弦和余弦坐标值。它能很好地体现一天、一周或一年等周期性时间的特征。
与简单的类别编码(one-hot encoding)不同,这种方法将时间转化为数值特征,相邻时间点的特征值也相对接近,而相距较远的时间点的特征值则相去甚远。这样可以保留时间序列的关联性,而类别编码会丢失这种信息。
我们可以将单位圆的0度(3点钟方向)作为起始点,对应0:00(午夜)。然后按逆时针方向,将圆周等分为4个象限,分别对应上午6点、中午12点、下午6点和午夜12点。任意一个时间戳都可以映射到对应的象限中,从而获得其唯一的正弦和余弦坐标值,这两个值就代表了该时间戳的数值特征。通过这种方式,我们可以用这对正弦余弦值来周期性地表示一天24小时的时间序列。
时间序列数据有循环周期性的特点,比如一天24小时就是一个循环。我们希望编码后的特征值能够体现这种循环关系,即相邻的时间点特征值相近,而时间间隔越大,特征值差异就越大。正弦余弦函数本身具有周期性,非常适合表示这种循环模式。
以每天24小时为例,我们将时间映射到单位圆上。圆周代表一天,设圆心为原点(0,0),半径为1。我们可将0点(午夜)设为起点,对应圆周上(1,0)的位置,并按逆时针方向进行。那么:
对于任意一个时间t,我们就可以根据它在圆周上的位置,计算出其对应的(cos(t), sin(t))坐标值。这对坐标值就是该时间点的正弦余弦特征编码。
对于其他周期性时间序列,如一周七天、一年十二个月等,也可类似地将其映射到单位圆并编码为正余弦值对。甚至可将多个不同的周期合并编码。
基本单位圆可以将相同的方法应用于其他周期,比如星期或年。在Python中实现这一点,首先需要将日期时间(在我这个例子中是每小时的时间戳)转换为数值变量。通过将此列转换为pd.Timestamp.timestamp对象,我们可以将每个时间戳转换为Unix时间(从1970年1月1日以来已过去的秒数)。
此时,可以将此数值列转换为正弦和余弦特征。
# 将日期时间转换为数字秒时间戳对象
# (tells you the date/time in seconds)
timestamp_s = df['Datetime'].map(pd.Timestamp.timestamp)
# 获取每个时间段的秒数
day = 24*60*60
week = day*7
year = day*(365.2425)
# 使用正弦和余弦进行变换
# Time of day
df['Day_sin'] = np.sin(timestamp_s * (2 * np.pi / day))
df['Day_cos'] = np.cos(timestamp_s * (2 * np.pi / day))
# Time of week
df['Week_sin'] = np.sin(timestamp_s * (2 * np.pi / week))
df['Week_cos'] = np.cos(timestamp_s * (2 * np.pi / week))
# Time of year
df['Year_sin'] = np.sin(timestamp_s * (2 * np.pi / year))
df['Year_cos'] = np.cos(timestamp_s * (2 * np.pi / year))
大致而言,我们首先需要将时间戳从秒转换为弧度,即乘以2 * np.pi
,因为一个完整的圆/周期有 2 * pi
的弧度。然后,我们将结果除以周期,这样就能以秒(日、周或年)为单位得到周期持续时间。接下来,通过乘以弧度数,我们将每个时间戳映射到一个唯一的角度,表示其在周期中的位置。
例如,如果周期为天,一天开始时的时间戳将被映射为 0 弧度,一天中间的时间戳将被映射为 np.pi
弧度,一天结束时的时间戳将被映射为 2 * np.pi
弧度。
最后,我们计算结果的 和 值,得到单位圆上实际的 x 和 y 坐标值。这些值将始终介于 -1 和 1 之间。
通过这种方法,每个原始时间序列特征(如每天的小时、每周的天、每年的月)现在只映射到 2 个新特征(原始特征的正弦和余弦),而不是 24、7、12 等。
在使用正弦余弦编码时间序列特征的方法时,需要格外谨慎并注意以下几点:
往期推荐阅读
长按👇关注- 数据STUDIO -设为星标,干货速递