如何使用.where()方法替换Timestamp的部分字段?
需求与问题
我有一个带Timestamp类型字段的DataFrame,需要筛选出不满足条件的时间戳,结合测试时间戳的年月日和原时间戳的小时部分,生成新的时间戳值。
示例DataFrame代码:
import pandas as pd df = pd.DataFrame(data={'col1': [pd.Timestamp(2021, 1, 1, 12), pd.Timestamp(2021, 1, 2, 12), pd.Timestamp(2021, 1, 3, 12)], 'col2': [pd.Timestamp(2021, 1, 4, 12), pd.Timestamp(2021, 1, 5, 12), pd.Timestamp(2021, 1, 6, 12)]}) print(df) # col1 col2 # 0 2021-01-01 12:00:00 2021-01-04 12:00:00 # 1 2021-01-02 12:00:00 2021-01-05 12:00:00 # 2 2021-01-03 12:00:00 2021-01-06 12:00:00
我尝试了这段代码:
testDate = pd.Timestamp(2021, 1, 2, 16) df['newCol'] = df['col1'].where(df['col1'].dt.date <= testDate.date(), pd.Timestamp(year=testDate.year, month=testDate.month, day=testDate.day, hour=df['col1'].dt.hour))
结果抛出了歧义错误:
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
去掉hour=df['col1'].dt.hour后代码能正常运行,说明问题出在这个参数上。我原以为是迭代值的问题,但用整数做类似逻辑时代码完全正常:
df = pd.DataFrame(data={'col1': [1,2,3], 'col2': [4,5,6]}) print(df) # col1 col2 # 0 1 4 # 1 2 5 # 2 3 6 testInt = 2 df['newCol'] = df['col1'].where(df['col1'] < testInt, df['col1'] + 2) print(df) # col1 col2 newCol # 0 1 4 1 # 1 2 5 4 # 2 3 6 5
想知道正确的实现方式是什么?
问题原因与解决办法
错误根源
pd.Timestamp()是用来生成单个时间戳的函数,它的参数必须是单个标量值,但你传入的df['col1'].dt.hour是一整列数据(Series类型),函数无法处理一组值,因此抛出了歧义错误。而整数测试中你是直接对整列做算术运算,这是Pandas原生支持的向量化操作,所以没问题。
正确实现方式
必须用向量化的方式构造整列新时间戳,以下是三种常用方案:
方案1:用pd.to_datetime()拼接字段(推荐大数据集)
先构造包含年、月、日、小时的临时DataFrame,再批量转换为时间戳:
import pandas as pd testDate = pd.Timestamp(2021, 1, 2, 16) # 构造新时间戳的各个组件 ts_components = pd.DataFrame({ 'year': testDate.year, 'month': testDate.month, 'day': testDate.day, 'hour': df['col1'].dt.hour }) # 批量转换为时间戳 new_ts = pd.to_datetime(ts_components) # 用where完成赋值 df['newCol'] = df['col1'].where(df['col1'].dt.date <= testDate.date(), new_ts)
方案2:用apply逐个替换(适合小数据集,代码直观)
对每个元素单独处理,用原时间戳的小时替换测试时间戳的小时:
testDate = pd.Timestamp(2021, 1, 2, 16) df['newCol'] = df['col1'].where( df['col1'].dt.date <= testDate.date(), df['col1'].apply(lambda x: testDate.replace(hour=x.hour)) )
方案3:向量化替换小时(高效简洁)
先构造全是测试时间戳的Series,再批量替换小时字段:
testDate = pd.Timestamp(2021, 1, 2, 16) # 生成和df长度一致的测试时间戳Series base_ts = pd.Series([testDate]*len(df), index=df.index) # 批量替换小时 new_ts = base_ts.dt.replace(hour=df['col1'].dt.hour) # 赋值 df['newCol'] = df['col1'].where(df['col1'].dt.date <= testDate.date(), new_ts)
验证结果
运行后df['newCol']的结果为:
0 2021-01-01 12:00:00 1 2021-01-02 12:00:00 2 2021-01-02 12:00:00 Name: newCol, dtype: datetime64[ns]
完全符合预期:前两行col1的日期小于等于测试日期,保留原值;第三行col1日期大于测试日期,替换为测试日期的年月日+原时间戳的12点。
内容的提问来源于stack exchange,提问作者MKF
相关产品推荐
相关产品推荐

