如何在Python(pandas)中从日期列获取周开始日期(周一)?

38

我看到很多关于如何使用日期字符串的帖子,但我正在尝试处理数据框列,但迄今为止没有什么好运。

我的当前方法是:从'myday'获取工作日,然后偏移以获取星期一。

df['myday'] is column of dates. 
mydays = pd.DatetimeIndex(df['myday']).weekday
df['week_start'] = pd.DatetimeIndex(df['myday']) - pd.DateOffset(days=mydays)

但是我遇到了TypeError: unsupported type for timedelta days component: numpy.ndarray

我该如何从数据框的一列中获取这周的开始日期?

5个回答

53

另一个选择:

df['week_start'] = df['myday'].dt.to_period('W').apply(lambda r: r.start_time)

这将设置“week_start”为“myday”之前的第一个星期一。

您可以通过锚定偏移量选择不同的星期开始日期,例如'W-THU'可使星期从周四开始。(感谢@Henry Ecker提出的建议)


7
谢谢。df['myday'].dt.to_period('W').dt.start_time 可能比使用 apply 更快(不确定这是从哪个 pandas 版本开始引入的,可能不适用于旧版本)。 - Ryan Tam
3
可以使用锚定偏移量来设置一周的任意开始日期。以星期日为开始日期的偏移量是'W-SUN',以星期四为开始日期的偏移量是'W-THU' - Henry Ecker
@HenryEcker 谢谢!这应该被添加到这个答案中,以覆盖更广泛的方面。 - Itachi
可以参考文档来了解可能的 .dt 选项有哪些?链式调用 .dt 并不是预期中的,但确实很棒。 - Luis R. Gonzalez

42
虽然@knightofni@Paul的解决方案都可以工作,但我倾向于避免在Pandas中使用apply,因为它通常比基于数组的方法慢得多。为了避免这种情况,在将列转换为日期时间列(通过pd.to_datetime)后,我们可以修改基于工作日的方法,并将星期几简单地转换为numpy timedelta64[D],方法是直接进行类型转换:
df['week_start'] = df['myday'] - df['myday'].dt.weekday.astype('timedelta64[D]')

或者使用 to_timedelta,就像 @ribitskiyb 建议的那样

df['week_start'] = df['myday'] - pd.to_timedelta(df['myday'].dt.weekday, unit='D'). 

使用包含60,000个日期时间的测试数据,我使用新发布的Pandas 1.0.1版本并按照建议的答案得到了以下时间。

%timeit df.apply(lambda x: x['myday'] - datetime.timedelta(days=x['myday'].weekday()), axis=1)
>>> 1.33 s ± 28.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit df['myday'].dt.to_period('W').apply(lambda r: r.start_time)
>>> 5.59 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit df['myday'] - df['myday'].dt.weekday.astype('timedelta64[D]')
>>> 3.44 ms ± 106 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit df['myday'] - pd.to_timedelta(df['myday'].dt.weekday, unit='D')
>>> 3.47 ms ± 170 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

这些结果显示,Pandas 1.0.1极大地提高了基于to_period apply方法的速度(相对于Pandas <= 0.25),但是直接转换为时间差(通过将类型直接转换为.astype('timedelta64[D]')或使用pd.to_timedelta)仍然更优。根据这些结果,建议今后使用pd.to_timedelta。

这是如何工作的?在我的数据集中,df ['myday'] .dt.weekday.astype('timedelta64 [D]')返回一系列全零。为什么或如何从df ['myday']中减去0会起作用?这似乎是最好的解决方案。 - Don Quixote
澄清上面的帖子,我理解的情况是基本上它在说取日期,然后从中减去星期几。但我不明白的是为什么.astype('timedelta64[D]')的结果都是零。 - Don Quixote
@DonQuixote 可能你的 'myday' 频率小于 '1D'。那么这样是行不通的。你必须修改它以减去小时、分钟等。 - grabantot

12

(只是在补充 n8yoder 的答案)

对我来说,使用.astype('timedelta64[D]')不太易读--我发现了一种替代方法,只使用 pandas 的功能:

df['myday'] - pd.to_timedelta(arg=df['myday'].dt.weekday, unit='D')

我喜欢@Paul的lambda表达式,但这个答案更快,同样“简洁”。感谢您提供的解决方案。 - Edmund's Echo

11

它失败的原因是pd.DateOffset期望参数为单个整数(而您正在提供一个数组)。您只能使用DateOffset通过相同的偏移量更改日期列。

尝试这样做:

import datetime as dt
# Change 'myday' to contains dates as datetime objects
df['myday'] = pd.to_datetime(df['myday'])  
# 'daysoffset' will container the weekday, as integers
df['daysoffset'] = df['myday'].apply(lambda x: x.weekday())
# We apply, row by row (axis=1) a timedelta operation
df['week_start'] = df.apply(lambda x: x['myday'] - dt.TimeDelta(days=x['daysoffset']), axis=1)

我实际上没有测试过这段代码(因为没有样本数据),但是根据你描述的应该可以工作。

不过,你可能想看一下pandas.Resample,它可能会提供更好的解决方案 - 具体取决于你所寻找的内容。


谢谢您的解释。这个解决方案正是我想要的! - dev28
1
TimeDelta 不应该指向 df['daysoffset'] 吗? - Carsten
使用lambda会导致某些操作变慢。 - Nicolas

3
from datetime import datetime, timedelta

# Convert column to pandas datetime equivalent
df['myday'] = pd.to_datetime(df['myday']) 

# Create function to calculate Start Week date
week_start_date = lambda date: date - timedelta(days=date.weekday())

# Apply above function on DataFrame column
df['week_start_date'] = df['myday'].apply(week_start_date)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接