Pandas:快速向时间戳列添加可变数量的月份

5 投票
3 回答
3722 浏览
提问于 2025-04-30 17:16

这是一个设置:

我有两列数据,分别是 startmonth_deltastart 里存的是时间戳(它的内部类型是 np.datetime64[ns]),而 month_delta 则是整数。

我想要快速生成一个新列,这个新列的每个时间都来自 start,并且根据 month_delta 中对应的月份数进行偏移。请问该怎么做呢?

我尝试过的一些方法,但都不奏效:

  • apply 方法太慢了。
  • 你不能把一系列的 DateOffset 对象加到 datetime64[ns] 类型的系列(或者 DatetimeIndex)上。
  • 你也不能使用 timedelta64 对象的系列;Pandas 会默默地把基于月份的时间差转换成大约 30 天的纳秒时间差。(哎呀!这是什么情况?)

目前,我正在遍历 month_delta 的所有不同值,并在我创建的 DatetimeIndex 的相关部分上进行相应的 tshift 操作,但这真是个糟糕的临时解决方案:

new_dates = pd.Series(pd.Timestamp.now(), index=start.index)
date_index = pd.DatetimeIndex(start)
for i in xrange(month_delta.max()):
    mask = (month_delta == i)
    cur_dates = pd.Series(index=date_index[mask]).tshift(i, freq='M').index
    new_dates[mask] = cur_dates

真让人头疼!有没有什么建议呢?

暂无标签

3 个回答

0

我找不到不使用至少一个 apply 来设置的方法,但假设这样做是可以的:

df = pandas.DataFrame(
    [[datetime.date(2014,10,22), 1], [datetime.date(2014,11,20), 2]], 
    columns=['date','delta'])
>>> df
         date  delta
0  2014-10-22      1
1  2014-11-20      2

from dateutil.relativedelta import relativedelta

df['offset'] = df['delta'].apply(lambda x: relativedelta(months=x))
>>> df['date'] + df['offset']
0    2014-11-22
1    2015-01-20

请注意,你必须使用 datetime 这个来自 datetime 模块的,而不是 numpypandas 的。因为你只是通过 apply 来创建时间差,我希望这样能让你体验到速度的提升。

0

我觉得像这样的代码可能会有效:

df['start'] = pd.to_datetime(df.start)
df.groupby('month_delta').apply(lambda x: x.start + pd.DateOffset(months=x.month_delta.iloc[0]))

也许还有更好的方法来创建一系列的 DateOffset 对象,并以某种方式把它们加起来,但我不太确定...

3

这里有一种方法可以做到这一点(通过将NumPy的datetime64和timedelta64相加),而不需要使用apply

import pandas as pd
import numpy as np
np.random.seed(1)

def combine64(years, months=1, days=1, weeks=None, hours=None, minutes=None,
              seconds=None, milliseconds=None, microseconds=None, nanoseconds=None):
    years = np.asarray(years) - 1970
    months = np.asarray(months) - 1
    days = np.asarray(days) - 1
    types = ('<M8[Y]', '<m8[M]', '<m8[D]', '<m8[W]', '<m8[h]',
             '<m8[m]', '<m8[s]', '<m8[ms]', '<m8[us]', '<m8[ns]')
    vals = (years, months, days, weeks, hours, minutes, seconds,
            milliseconds, microseconds, nanoseconds)
    return sum(np.asarray(v, dtype=t) for t, v in zip(types, vals)
               if v is not None)

def year(dates):
    "Return an array of the years given an array of datetime64s"
    return dates.astype('M8[Y]').astype('i8') + 1970

def month(dates):
    "Return an array of the months given an array of datetime64s"
    return dates.astype('M8[M]').astype('i8') % 12 + 1

def day(dates):
    "Return an array of the days of the month given an array of datetime64s"
    return (dates - dates.astype('M8[M]')) / np.timedelta64(1, 'D') + 1

N = 10
df = pd.DataFrame({
   'start': pd.date_range('2000-1-25', periods=N, freq='D'),
   'months': np.random.randint(12, size=N)})
start = df['start'].values
df['new_date'] = combine64(year(start), months=month(start) + df['months'], 
                           days=day(start))

print(df)

结果是

   months      start   new_date
0       5 2000-01-25 2000-06-25
1      11 2000-01-26 2000-12-26
2       8 2000-01-27 2000-09-27
3       9 2000-01-28 2000-10-28
4      11 2000-01-29 2000-12-29
5       5 2000-01-30 2000-06-30
6       0 2000-01-31 2000-01-31
7       0 2000-02-01 2000-02-01
8       1 2000-02-02 2000-03-02
9       7 2000-02-03 2000-09-03

撰写回答