查看全课大纲(第 17 / 35 节)

时间特征

约 7 分钟

小象实战讲义 · 特征工程精讲与实践

在数据科学项目中,时间信息是蕴含丰富业务规律的“富矿”。无论是电商销量、用户活跃度还是设备运行状态,其变化往往与时间周期紧密相关。本节我们将系统学习如何从原始的日期/时间字符串中,抽取有意义的特征,例如月份、星期几、是否为周末、距离特定日期的间隔等,从而让模型能够捕捉到时间维度的模式。

💡 核心导读

  • 时间特征的价值:理解时间型数据(如日期、时间戳)在刻画业务周期、趋势和间隔方面的核心作用。
  • 类型转换是第一步:掌握使用 pandas.to_datetime() 将字符串转换为日期时间类型,这是所有后续操作的基础。
  • 关键信息抽取:学习通过 .dt 访问器轻松提取年、月、日、星期、季度、一年中的第几天等结构化特征。
  • 时间间隔计算:学会计算两个日期之间的差值,生成如“距离某个促销日的天数”等连续值特征。

时间型数据特征工程概述

时间型数据,如 2021-12-25、2023-08-15 14:30:00 等,在原始数据集中通常以字符串(object 类型)存储。为了从中挖掘信息,我们需要将其转换为程序能够理解的日期时间类型,进而抽取对预测目标有影响的维度。

根据课件,时间特征主要分为三类:

  1. 日期型数据特征:如年、月、日、星期几、季度、一年中的第几周等。
  2. 一天中的时间特征:如小时、分钟、所属时间段(上午、下午、夜晚)等。
  3. 时间间隔特征:计算两个时间点之间的差值,常用于用户行为序列分析,如“距离上一次购买的天数”、“平均活跃间隔”等。

本节我们将通过一个汽车销量数据集,演示如何完成从类型转换到特征构建的完整流程。

实战:汽车销量数据的时间特征构建

本节我们将使用一份模拟的汽车月度销量数据。在真实工业场景中,你拿到的原始数据很可能就是一个日期字符串列和一个数值列。我们的目标是基于日期字符串,构造出更多有助于销量预测的时间特征。

步骤一:数据读取与审视

首先,我们创建一份最小化的示例数据来模拟 car_data.csv 的形态。这份数据包含日期字符串和对应的销量计数。

import pandas as pd
import numpy as np

# 构造一份小规模示例数据,模拟汽车月度销量
data = {
    'date_t': ['2021-01-15', '2021-02-20', '2021-03-10', '2021-04-05', '2021-05-25',
               '2021-06-18', '2021-07-22', '2021-08-30', '2021-09-12', '2021-10-08'],
    'count': [125.0, 138.0, 119.0, 156.0, 167.0, 145.0, 178.0, 190.0, 132.0, 148.0]
}
car_sales = pd.DataFrame(data)

print("原始数据预览:")
print(car_sales)
print("\n数据信息 (info):")
print(car_sales.info())
原始数据预览:
       date_t  count
0  2021-01-15  125.0
1  2021-02-20  138.0
2  2021-03-10  119.0
3  2021-04-05  156.0
4  2021-05-25  167.0
5  2021-06-18  145.0
6  2021-07-22  178.0
7  2021-08-30  190.0
8  2021-09-12  132.0
9  2021-10-08  148.0

数据信息 (info):
<class 'pandas.DataFrame'>
RangeIndex: 10 entries, 0 to 9
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   date_t  10 non-null     str
 1   count   10 non-null     float64
dtypes: float64(1), str(1)
memory usage: 292.0 bytes
None

从 info 可以看出,date_t 字段是字符串类型(pandas 3.0 中显示为 str;旧版本 pandas 显示为 object,原课程录制时即为 object),而 count 是数值型 float64。字符串类型的日期无法直接进行时间运算,因此下一步必须进行类型转换。

步骤二:日期时间类型转换

使用 pandas.to_datetime() 函数,我们可以轻松地将格式规范的日期字符串列转换为 datetime64 类型。转换后,pandas 会识别其日期时间属性,并允许我们通过 .dt 访问器提取各部分信息。

import pandas as pd

# 重新构造数据,确保本代码块自包含
data = {
    'date_t': ['2021-01-15', '2021-02-20', '2021-03-10', '2021-04-05', '2021-05-25',
               '2021-06-18', '2021-07-22', '2021-08-30', '2021-09-12', '2021-10-08'],
    'count': [125.0, 138.0, 119.0, 156.0, 167.0, 145.0, 178.0, 190.0, 132.0, 148.0]
}
car_sales = pd.DataFrame(data)

# 核心操作:将字符串列转换为 datetime64 类型,并创建新列 ‘date’
car_sales['date'] = pd.to_datetime(car_sales['date_t'])

print("转换后数据信息:")
print(car_sales.info())
print("\n转换后数据预览(注意新增的 date 列,其显示与 date_t 相同,但内部类型已变):")
print(car_sales[['date_t', 'date', 'count']])
转换后数据信息:
<class 'pandas.DataFrame'>
RangeIndex: 10 entries, 0 to 9
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   date_t  10 non-null     str
 1   count   10 non-null     float64
 2   date    10 non-null     datetime64[us]
dtypes: datetime64[us](1), float64(1), str(1)
memory usage: 372.0 bytes
None

转换后数据预览(注意新增的 date 列,其显示与 date_t 相同,但内部类型已变):
       date_t       date  count
0  2021-01-15 2021-01-15  125.0
1  2021-02-20 2021-02-20  138.0
2  2021-03-10 2021-03-10  119.0
3  2021-04-05 2021-04-05  156.0
4  2021-05-25 2021-05-25  167.0
5  2021-06-18 2021-06-18  145.0
6  2021-07-22 2021-07-22  178.0
7  2021-08-30 2021-08-30  190.0
8  2021-09-12 2021-09-12  132.0
9  2021-10-08 2021-10-08  148.0

现在,date 列的类型是 datetime64[us](新版本 pandas 的默认精度;旧版本默认为 ns,对本节的特征抽取没有任何影响)。虽然打印出来和 date_t 看起来一样,但它已经具备了时间内涵,我们可以从中“拆解”出大量特征。

步骤三:抽取关键时间维度特征

转换成功后,通过 .dt 访问器,我们可以像访问对象属性一样,提取日期时间的各个组成部分。这是构建时间特征最常用、最直接的方法。

import pandas as pd
from datetime import datetime

# 重新构造包含日期时间类型列的数据
data = {
    'date_t': ['2021-01-15', '2021-02-20', '2021-03-10', '2021-04-05', '2021-05-25',
               '2021-06-18', '2021-07-22', '2021-08-30', '2021-09-12', '2021-10-08'],
    'count': [125.0, 138.0, 119.0, 156.0, 167.0, 145.0, 178.0, 190.0, 132.0, 148.0]
}
car_sales = pd.DataFrame(data)
car_sales['date'] = pd.to_datetime(car_sales['date_t'])

# 1. 抽取月份 (1-12)
car_sales['month'] = car_sales['date'].dt.month

# 2. 抽取日期 (几号,1-31)
car_sales['day_of_month'] = car_sales['date'].dt.day

# 3. 抽取一年中的第几天 (1-365/366)
car_sales['day_of_year'] = car_sales['date'].dt.dayofyear

# 4. 抽取星期几 (0=Monday, 6=Sunday)
car_sales['day_of_week'] = car_sales['date'].dt.dayofweek

# 5. 基于星期几判断是否为周末 (这里定义周六、日为周末)
car_sales['is_weekend'] = car_sales['day_of_week'].apply(lambda x: 1 if x >= 5 else 0)

# 6. 计算时间间隔:以2021年12月25日为基准,计算每个日期距离基准的天数
reference_date = datetime(2021, 12, 25)
car_sales['days_to_ref'] = (reference_date - car_sales['date']).dt.days

print("完成特征构建后的数据:")
print(car_sales[['date_t', 'month', 'day_of_month', 'day_of_year', 'day_of_week', 'is_weekend', 'days_to_ref', 'count']])
完成特征构建后的数据:
       date_t  month  day_of_month  ...  is_weekend  days_to_ref  count
0  2021-01-15      1            15  ...           0          344  125.0
1  2021-02-20      2            20  ...           1          308  138.0
2  2021-03-10      3            10  ...           0          290  119.0
3  2021-04-05      4             5  ...           0          264  156.0
4  2021-05-25      5            25  ...           0          214  167.0
5  2021-06-18      6            18  ...           0          190  145.0
6  2021-07-22      7            22  ...           0          156  178.0
7  2021-08-30      8            30  ...           0          117  190.0
8  2021-09-12      9            12  ...           1          104  132.0
9  2021-10-08     10             8  ...           0           78  148.0

[10 rows x 8 columns]

代码解读与 .dt 访问器:

  • .dt.month, .dt.day, .dt.dayofyear, .dt.dayofweek 等都是 datetime64 系列列的内置属性,直接返回整数。
  • day_of_week 返回 0-6,代表周一到周日。这是一种序号编码(Label Encoding)。
  • is_weekend 是我们根据业务逻辑(定义周六、日为周末)衍生出的二值特征(0/1),这是一个简单的特征衍生过程。
  • days_to_ref 展示了如何计算时间间隔。两个 datetime 对象相减,得到一个 Timedelta 对象,再通过 .dt.days 提取整数天数。这种“距离某个关键日期(如促销日、节假日前)的天数”是预测模型中非常有效的特征。

通过以上几步,我们成功地将一个原始的日期字符串列,扩展成了包含月份、月中日期、年中日期、星期类型、周末标志以及特定时间间隔在内的多个特征,极大地丰富了模型的信息输入。

📝 动手练一练

  1. 概念辨析:在时间特征工程中,为什么必须先将 object 类型的日期字符串转换为 datetime64 类型?如果跳过这一步直接对字符串进行操作,可能会遇到哪些问题?
👉 点击查看参考答案

参考答案: 必须进行类型转换是因为:

  • 语义识别:datetime64 类型让 pandas 和后续机器学习库能够理解该列数据代表时间,从而支持基于时间的排序、切片和运算。
  • 特征抽取:只有转换后,才能使用 .dt 访问器方便地提取年、月、日、小时、星期等结构化成分。对字符串操作需要复杂的字符串解析(如切片、正则匹配),容易出错且不通用。
  • 时间运算:计算间隔、比较先后等操作只能在时间类型上进行。字符串无法直接进行减法等运算。
  • 性能与一致性:pandas 对 datetime64 类型有优化,处理效率更高,且能自动处理闰年、时区等复杂情况,保证结果一致性。
  1. 动手实践:假设你有一份用户登录记录,包含登录时间戳 login_time(格式如 2023-08-15 14:30:00)。请编写代码,从中提取出“小时”(hour)和“是否属于工作时间”(is_work_hour,假设工作时间为 9:00-18:00)两个特征。
👉 点击查看参考答案

参考答案:

import pandas as pd

# 示例用户登录数据
login_data = {
    'user_id': [1, 2, 3, 4, 5],
    'login_time_str': ['2023-08-15 09:15:00', '2023-08-15 14:30:00', 
                       '2023-08-15 20:45:00', '2023-08-16 10:00:00', '2023-08-16 18:30:00']
}
df_login = pd.DataFrame(login_data)

# 1. 类型转换
df_login['login_time'] = pd.to_datetime(df_login['login_time_str'])

# 2. 抽取小时
df_login['hour'] = df_login['login_time'].dt.hour

# 3. 判断是否为工作时间 (9点至18点之间,包含9点不包含18点)
df_login['is_work_hour'] = df_login['hour'].apply(lambda h: 1 if 9 <= h < 18 else 0)

print(df_login[['user_id', 'login_time_str', 'hour', 'is_work_hour']])
user_id       login_time_str  hour  is_work_hour
0        1  2023-08-15 09:15:00     9             1
1        2  2023-08-15 14:30:00    14             1
2        3  2023-08-15 20:45:00    20             0
3        4  2023-08-16 10:00:00    10             1
4        5  2023-08-16 18:30:00    18             0

本章小结

本节我们深入学习了时间型数据的特征工程方法。时间特征的核心在于从单一的时间点信息中,解构出对业务有解释力的多个维度。

📋 行动清单

  • 掌握类型转换:面对日期/时间字符串,第一反应应是使用 pd.to_datetime() 进行转换。
  • 熟练使用 .dt 访问器:记住常用属性如 .month, .day, .dayofweek, .hour 等,并能快速从日期时间列中抽取这些特征。
  • 思考业务场景:在构建时间特征时,始终联系业务。例如,对于销量预测,月份、星期几、是否节假日、距离大促日天数可能都是关键特征;对于用户行为分析,一天中的时段、操作间隔频率则更为重要。

—— 小象教研组