【问题标题】:Pandas: How to create a datetime object from Week and Year?Pandas:如何从周和年创建日期时间对象?
【发布时间】:2018-01-08 06:21:00
【问题描述】:

我有一个数据框,它提供了两个整数列,其中包含一年中的年份和星期:

import pandas as pd
import numpy as np
L1 = [43,44,51,2,5,12]
L2 = [2016,2016,2016,2017,2017,2017]
df = pd.DataFrame({"Week":L1,"Year":L2})

df
Out[72]: 
   Week  Year
0    43  2016
1    44  2016
2    51  2016
3     2  2017
4     5  2017
5    12  2017

我需要从这两个数字创建一个日期时间对象。

我试过了,但它抛出了一个错误:

df["DT"] = df.apply(lambda x: np.datetime64(x.Year,'Y') + np.timedelta64(x.Week,'W'),axis=1)

然后我尝试了这个,它有效但给出了错误的结果,即它完全忽略了一周:

df["S"] = df.Week.astype(str)+'-'+df.Year.astype(str)
df["DT"] = df["S"].apply(lambda x: pd.to_datetime(x,format='%W-%Y'))

df
Out[74]: 
   Week  Year        S         DT
0    43  2016  43-2016 2016-01-01
1    44  2016  44-2016 2016-01-01
2    51  2016  51-2016 2016-01-01
3     2  2017   2-2017 2017-01-01
4     5  2017   5-2017 2017-01-01
5    12  2017  12-2017 2017-01-01

我真的在 Python 的 datetime、Numpy 的 datetime64 和 pandas Timestamp 之间迷路了,你能告诉我它是如何正确完成的吗?

我正在使用 Python 3,如果这在任何方面都相关的话。

编辑:

从 Python 3.8 开始,使用 datetime.date 对象上新引入的方法可以轻松解决该问题:https://docs.python.org/3/library/datetime.html#datetime.date.fromisocalendar

【问题讨论】:

  • Week 的值是 ISO week numbers 还是代表 7 天的单位?
  • 最初我在s 中有时间戳,它们使用pd.to_datetime() 转换,然后使用时间戳上的dt.week 提取星期。
  • 这里有一个微妙的陷阱——如果s包含日期2016-1-1,那么它的ISO周数(由dt.week返回)是53,它的ISO年(你没有't record) 是 2015 年。如果您尝试使用 2016 年和 ISO 第 53 周来重构日期,那么您会得到 2017-01-02(假设星期一开始一周)。因此,除非您还记录 ISO 年份(并不总是与实际年份相同),否则您无法正确往返。
  • @unutbu 谢谢,很高兴知道这一点。

标签: python pandas datetime numpy


【解决方案1】:

试试这个:

In [19]: pd.to_datetime(df.Year.astype(str), format='%Y') + \
             pd.to_timedelta(df.Week.mul(7).astype(str) + ' days')
Out[19]:
0   2016-10-28
1   2016-11-04
2   2016-12-23
3   2017-01-15
4   2017-02-05
5   2017-03-26
dtype: datetime64[ns]

最初我在s中有时间戳

从 UNIX 纪元时间戳解析它要容易得多:

df['Date'] = pd.to_datetime(df['UNIX_Time'], unit='s')

时间为 10M 行 DF:

设置:

In [26]: df = pd.DataFrame(pd.date_range('1970-01-01', freq='1T', periods=10**7), columns=['date'])

In [27]: df.shape
Out[27]: (10000000, 1)

In [28]: df['unix_ts'] = df['date'].astype(np.int64)//10**9

In [30]: df
Out[30]:
                       date    unix_ts
0       1970-01-01 00:00:00          0
1       1970-01-01 00:01:00         60
2       1970-01-01 00:02:00        120
3       1970-01-01 00:03:00        180
4       1970-01-01 00:04:00        240
5       1970-01-01 00:05:00        300
6       1970-01-01 00:06:00        360
7       1970-01-01 00:07:00        420
8       1970-01-01 00:08:00        480
9       1970-01-01 00:09:00        540
...                     ...        ...
9999990 1989-01-05 10:30:00  599999400
9999991 1989-01-05 10:31:00  599999460
9999992 1989-01-05 10:32:00  599999520
9999993 1989-01-05 10:33:00  599999580
9999994 1989-01-05 10:34:00  599999640
9999995 1989-01-05 10:35:00  599999700
9999996 1989-01-05 10:36:00  599999760
9999997 1989-01-05 10:37:00  599999820
9999998 1989-01-05 10:38:00  599999880
9999999 1989-01-05 10:39:00  599999940

[10000000 rows x 2 columns]

检查:

In [31]: pd.to_datetime(df.unix_ts, unit='s')
Out[31]:
0         1970-01-01 00:00:00
1         1970-01-01 00:01:00
2         1970-01-01 00:02:00
3         1970-01-01 00:03:00
4         1970-01-01 00:04:00
5         1970-01-01 00:05:00
6         1970-01-01 00:06:00
7         1970-01-01 00:07:00
8         1970-01-01 00:08:00
9         1970-01-01 00:09:00
                  ...
9999990   1989-01-05 10:30:00
9999991   1989-01-05 10:31:00
9999992   1989-01-05 10:32:00
9999993   1989-01-05 10:33:00
9999994   1989-01-05 10:34:00
9999995   1989-01-05 10:35:00
9999996   1989-01-05 10:36:00
9999997   1989-01-05 10:37:00
9999998   1989-01-05 10:38:00
9999999   1989-01-05 10:39:00
Name: unix_ts, Length: 10000000, dtype: datetime64[ns]

时间:

In [32]: %timeit pd.to_datetime(df.unix_ts, unit='s')
10 loops, best of 3: 156 ms per loop

结论:我认为转换 10.000.000 行需要 156 毫秒并没有那么慢

【讨论】:

  • 也许直接使用时间戳确实是一个更好的主意。但是,我正在处理数千万行,而 datetime 的东西非常慢。
  • @Khris,是的,使用这种方法我们可以精确地转换它
  • @Khris,我已经添加了时间 - 请检查
  • 最初我在某一点进行转换,然后保存所有值以避免以后进行转换。但我没想到我需要像那样转换回来,所以最好直接从 epoch 时间戳开始。
  • @MaxU - 你能比较一下周解决方案吗?因为问题是关于使用weekyear 进行转换。
【解决方案2】:

就像@Gianmario Spacagna 提到的日期时间高于 2018 年,使用 %V%G

L1 = [43,44,51,2,5,12,52,53,1,2,5,52]
L2 = [2016,2016,2016,2017,2017,2017,2018,2018,2019,2019,2019,2019]
df = pd.DataFrame({"Week":L1,"Year":L2})


df['new'] = pd.to_datetime(df.Week.astype(str)+
                           df.Year.astype(str).add('-1') ,format='%V%G-%u')
print (df)
    Week  Year        new
0     43  2016 2016-10-24
1     44  2016 2016-10-31
2     51  2016 2016-12-19
3      2  2017 2017-01-09
4      5  2017 2017-01-30
5     12  2017 2017-03-20
6     52  2018 2018-12-24
7     53  2018 2018-12-31
8      1  2019 2018-12-31
9      2  2019 2019-01-07
10     5  2019 2019-01-28
11    52  2019 2019-12-23

【讨论】:

  • 奇怪,文档暗示第一天已经使用%W%U 定义:docs.python.org/3/library/…
  • 此方法仅适用于 2018 年。它不符合 ISO-8601,因为 2018 年 12 月 31 日被分配到 2018 年第 53 周而不是 2019 年第 1 周。导致所有后续日期时间提前 7 天。
  • @GianmarioSpacagna - 你能解释更多吗?
  • 尝试使用 Year = 2019 和 Week = 1,它将返回 2019-01-07 作为日期。正确的日期应该是 2018-12-31。来源:epochconverter.com/weeks/2019
  • 我认为 ISO 周的解决方案是使用 stackoverflow.com/a/17087427/2919826 中记录的不同格式字符串 (%G-W%V-%u)
【解决方案3】:

从 2019 年开始的几周内发生了一些可疑的事情。ISO-8601 标准将 2018 年 12 月 31 日指定为 2019 年的第 1 周。其他方法基于:

pd.to_datetime(df.Week.astype(str)+
                  df.Year.astype(str).add('-2') ,format='%W%Y-%w')

将从 2019 年开始给出不同的结果。

为了符合 ISO-8601 标准,您必须执行以下操作:

import pandas as pd
import datetime

L1 = [52,53,1,2,5,52]
L2 = [2018,2018,2019,2019,2019,2019]
df = pd.DataFrame({"Week":L1,"Year":L2})
df['ISO'] = df['Year'].astype(str) + '-W' + df['Week'].astype(str) + '-1'
df['DT'] = df['ISO'].map(lambda x: datetime.datetime.strptime(x, "%G-W%V-%u"))
print(df)

打印出来:

   Week  Year         ISO         DT
0    52  2018  2018-W52-1 2018-12-24
1    53  2018  2018-W53-1 2018-12-31
2     1  2019   2019-W1-1 2018-12-31
3     2  2019   2019-W2-1 2019-01-07
4     5  2019   2019-W5-1 2019-01-28
5    52  2019  2019-W52-1 2019-12-23

2018 年第 53 周被忽略并映射到 2019 年第 1 周。

请在https://www.epochconverter.com/weeks/2019 上验证自己。

【讨论】:

  • 观察力不错。 Python 3.8 在 datetime.date 对象上引入了一种新方法,可以解决整个问题:docs.python.org/3/library/…
  • 谢谢@Khris。不幸的是,大多数企业应用程序都在 python 3.6 上运行,因为大多数云提供商目前都支持托管环境,但很高兴知道未来。
【解决方案4】:

如果你想关注ISO Week Date

每周从星期一开始。每周的年份是公历年 星期四。因此,一年中的第一周,总是 包含 1 月 4 日。因此 ISO 周年编号略 在接近 1 月 1 日的时候有几天偏离公历。

以下示例代码生成一个包含 60 个日期的序列,从 18Dec2016 Sun 开始并添加相应的列。

它补充说:

  • 一个“日期”
  • “日期”的星期几
  • 查找从该“日期”的星期一开始的周
  • 查找从该“日期”的星期一开始的一周中的年份
  • 添加周数 (ISO)
  • 从年和周数获取星期一的开始日期

下面的示例代码:

# Generate Some Dates
dft1 = pd.DataFrame(pd.date_range('2016-12-18', freq='D', periods=60))
dft1.columns = ['e_FullDate']
dft1['e_FullDateWeekDay'] = dft1.e_FullDate.dt.day_name().str.slice(0,3)


#Add a Week Start Date (Monday)
dft1['e_week_start'] = dft1['e_FullDate'] - pd.to_timedelta(dft1['e_FullDate'].dt.weekday,
                                                      unit='D')
dft1['e_week_startWeekDay'] = dft1.e_week_start.dt.day_name().str.slice(0,3)

#Add a Week Start Year
dft1['e_week_start_yr'] = dft1.e_week_start.dt.year

#Add a Week Number of Week Start Monday
dft1['e_week_no'] = dft1['e_week_start'].dt.week

#Add a Week Start generate from Week Number and Year
dft1['e_week_start_from_week_no'] = pd.to_datetime(dft1.e_week_no.astype(str)+
                  dft1.e_week_start_yr.astype(str).add('-1') ,format='%W%Y-%w')
dft1['e_week_start_from_week_noWeekDay'] = dft1.e_week_start_from_week_no.dt.day_name().str.slice(0,3)


with pd.option_context('display.max_rows', 999, 'display.max_columns', 0, 'display.max_colwidth', 9999):
    display(dft1)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-06
    • 1970-01-01
    • 2020-08-21
    • 2011-07-14
    • 1970-01-01
    • 2017-04-04
    • 1970-01-01
    相关资源
    最近更新 更多