在 Pandas 日期时间列中标记夏令时 (DST) 小时

Posted

技术标签:

【中文标题】在 Pandas 日期时间列中标记夏令时 (DST) 小时【英文标题】:Flag Daylight Saving Time (DST) Hours in Pandas Date-Time Column 【发布时间】:2019-02-27 06:46:19 【问题描述】:

我创建了一个每小时的日期数据框,现在我想创建一个列来标记每一行(小时)是否处于夏令时。例如,在夏季时间,标志应该 == 1,而在冬季时间,标志应该 == 0。

# Localized dates dataframe
dates = pd.DataFrame(data=pd.date_range('2018-1-1', '2019-1-1', freq='h', tz='America/Denver'), columns=['date_time'])

# My failed attempt to create the flag column
dates['dst_flag'] = np.where(dates['date_time'].dt.daylight_saving_time == True, 1, 0)

【问题讨论】:

这可能必须是手动的。提取边界日期并执行一些条件测试。相关:get the DST boundaries of a given timezone in python. 似乎是 XY 问题。你真的关心夏令时,还是关心时区之间的时间转换?如果是后者,请在 internally 使用 UTC 并在 output 时转换为本地时区。 @MattMessersmith,我关心确定 DST 时间,因为我将它用作回归中的虚拟变量。它会影响电力消耗模式,因为消耗取决于日光。 哦,好吧,有趣!那么这不是 XY 问题。感谢您的澄清,只是想确定一下。 【参考方案1】:

cmets 中有一个不错的链接,至少可以让您手动执行此操作。 AFAIK,没有矢量化的方法可以做到这一点。

import pandas as pd
import numpy as np
from pytz import timezone

# Generate data (as opposed to index)                                                                                                                                                                                  
date_range = pd.to_datetime(pd.date_range('1/1/2018', '1/1/2019', freq='h', tz='America/Denver'))
date_range = [date for date in date_range]

# Localized dates dataframe                                                                                                                                                           
df = pd.DataFrame(data=date_range, columns=['date_time'])

# Map transition times to year for some efficiency gain                                                                                                                                                     
tz = timezone('America/Denver')
transition_times = tz._utc_transition_times[1:]
transition_times = [t.astimezone(tz) for t in transition_times]
transition_times_by_year = 
for start_time, stop_time in zip(transition_times[::2], transition_times[1::2]):
    year = start_time.year
    transition_times_by_year[year] = [start_time, stop_time]

# If the date is in DST, mark true, else false                                                                                                                                                              
def mark_dst(dates):
    for date in dates:
        start_dst, stop_dst = transition_times_by_year[date.year]
        yield start_dst <= date <= stop_dst
df['dst_flag'] = [dst_flag for dst_flag in mark_dst(df['date_time'])]

# Do a quick sanity check to make sure we did this correctly for year 2018                                                                                                                                  
dst_start = df[df['dst_flag'] == True]['date_time'][0] # First dst time 2018
dst_end = df[df['dst_flag'] == True]['date_time'][-1] # Last dst time 2018
print(dst_start)
print(dst_end)

这个输出:

2018-03-11 07:00:00-06:00
2018-11-04 06:00:00-07:00

这是可能正确的。我没有手动进行 UTC 转换或任何检查时间对于给定时区是否完全正确。您至少可以通过快速 google 搜索来验证日期是否正确。

一些陷阱:

    pd.date_range 生成索引,而不是数据。我稍微更改了您的原始代码,使其成为数据而不是索引。我假设你已经有了数据。

    tz._utc_transition_times 的结构有些愚蠢。这是开始/停止 UTC DST 转换时间,但早期有一些愚蠢的东西。不过从 1965 年开始应该会很好。如果您的日期早于该日期,请将tz._utc_transition_times[1:] 更改为tz._utc_transition_times。请注意,并非 1965 年之前的所有年份都存在。

    tz._utc_transition_times 是“Python 私有”。它可能会在没有警告或通知的情况下进行更改,并且可能会或可能不会适用于 pytz 的未来或过去版本。我正在使用pytz verion 2017.3。我建议您运行此代码以确保输出匹配,如果不匹配,请确保使用版本 2017.3。

HTH,祝你的研究/回归问题好运!

【讨论】:

非常感谢您对此提供的帮助。不幸的是,当我运行这一行时,它给了我一个错误:transition_times = [t.astimezone(tz) for t in transition_times]。错误是:OSError: [Errno 22] Invalid argument。可能是因为我用的是 pytz==2018.4 这是一个非常奇怪的错误。你能打印出tz._utc_transition_times[1:] 的前几个值吗?也许只是打印tz._utc_transition_times[1:5]。你用的是什么版本的pytz Out[183]​​: [datetime.datetime(1901, 12, 13, 20, 45, 52), datetime.datetime(1918, 3, 31, 9, 0), datetime.datetime( 1918, 10, 27, 8, 0), datetime.datetime(1919, 3, 30, 9, 0)] 我正在使用 pytz==2018.4 看起来不错。你能提供更多的堆栈跟踪吗?你确定你有正确的线路?您是否尝试过完全按原样运行示例(不复制/粘贴到您的应用程序中:而只是复制/粘贴到一个新文件中并从新文件中尝试)? 对不起,我对 Python 还是很陌生。这就是我认为你所要求的。我将您的代码复制到一个全新的文件中。回溯(最后一次调用):文件“”,第 1 行,在 transition_times = [t.astimezone(tz) for t in transition_times] 文件“",第 1 行,在 transition_times = [t.astimezone(tz) for t in transition_times] OSError: [Errno 22] Invalid argument【参考方案2】:

如果您正在寻找一种矢量化的方式来执行此操作(您可能应该这样做),您可以使用类似下面的代码。

这背后的基本思想是找出您所在时区的当前时间与 UTC 时间之间的差异。在冬季,时差将比 UTC 多一个小时。无论有什么区别,添加所需的内容以获取标志的 1 或 0。

在丹佛,夏季月份为 UTC-6,冬季月份为 UTC-7。因此,如果您将丹佛的 tz 感知时间与 UTC 时间之间的差加上 7,则夏季月份的值为 1,冬季月份的值为 0。

import pandas as pd

start = pd.to_datetime('2020-10-30')
end = pd.to_datetime('2020-11-02')
dates = pd.date_range(start=start, end=end, freq='h', tz='America/Denver')
df1 = pd.DataFrame('dst_flag': 1, 'date1': dates.tz_localize(None), index=dates)

# add extra day on each end so that there are no nan's after the join    
dates = pd.to_datetime(pd.date_range(start=start - pd.to_timedelta(1, 'd'), end=end + pd.to_timedelta(1, 'd'), freq='h'), utc=True)
df2 = pd.DataFrame('date2': dates.tz_localize(None), index=dates)
    
out = df1.join(df2)
out['dst_flag'] = (out['date1'] - out['date2']) / pd.to_timedelta(1, unit='h') + 7
out.drop(columns=['date1', 'date2'], inplace=True)

【讨论】:

【参考方案3】:

这是我最终要做的,它适用于我的目的:

import pandas as pd
import pytz

# Create dates table and flag Daylight Saving Time dates
dates = pd.DataFrame(data=pd.date_range('2018-1-1', '2018-12-31-23', freq='h'), columns=['date_time'])

# Create a list of start and end dates for DST in each year, in UTC time
dst_changes_utc = pytz.timezone('America/Denver')._utc_transition_times[1:]

# Convert to local times from UTC times and then remove timezone information
dst_changes = [pd.Timestamp(i).tz_localize('UTC').tz_convert('America/Denver').tz_localize(None) for i in dst_changes_utc]

flag_list = []
for index, row in dates['date_time'].iteritems():
    # Isolate the start and end dates for DST in each year
    dst_dates_in_year = [date for date in dst_changes if date.year == row.year]
    spring = dst_dates_in_year[0]
    fall = dst_dates_in_year[1]
    if (row >= spring) & (row < fall):
        flag = 1
    else:
        flag = 0
    flag_list.append(flag)
print(flag_list)
dates['dst_flag'] = flag_list
del(flag_list)

【讨论】:

【参考方案4】:

以下矢量化方式似乎工作正常。 背后的想法与 Nick Klavoht 的想法相同:找出您所在时区的当前时间与 UTC 时间之间的差异。

# Localized dates dataframe
df = pd.DataFrame(data=pd.date_range('2018-1-1', '2019-1-1', freq='h', tz='America/Denver'), columns=['date_time'])

df['utc_offset'] = df['date_time'].dt.strftime('%z').str[0:3].astype(float)
df['utc_offset_shifted'] = df['utc_offset'].shift(-1)
df['dst'] = df['utc_offset'] - df['utc_offset_shifted']
df_dst = df[(df['dst'] != 0) & (df['dst'])]
df_dst = df_dst.drop(['utc_offset', 'utc_offset_shifted'], axis=1).reset_index(drop=True)

print(df_dst)

这个输出:

                  date_time  dst
0 2018-03-11 01:00:00-07:00 -1.0
1 2018-11-04 01:00:00-06:00  1.0

【讨论】:

【参考方案5】:

如果您知道您正在处理的时区,您可以使用:

dates['dst_flag'] = dates['date_time'].apply(lambda x: x.tzname() == 'CEST')

这会将CET 中的所有时间标记为 False,CEST 中的所有时间标记为 True。我不确定我是否想在一个巨大的专栏上这样做。

【讨论】:

以上是关于在 Pandas 日期时间列中标记夏令时 (DST) 小时的主要内容,如果未能解决你的问题,请参考以下文章

确定夏令时 (DST) 在 Java 中是不是在指定日期处于活动状态

如何更改 p:calendar 组件中的夏令时 (DST) 日期

在 SQL/数据库级别计算夏令时 (DST)

在指定时区导入日期时间,忽略夏令时

在 DST 更改发生时将字符串解析为日期

夏令时更改为绝对日期