【问题标题】:Extracting the first day of month of a datetime type column in pandas提取熊猫中日期时间类型列的第一天
【发布时间】:2017-07-25 13:16:29
【问题描述】:

我有以下数据框:

user_id    purchase_date 
  1        2015-01-23 14:05:21
  2        2015-02-05 05:07:30
  3        2015-02-18 17:08:51
  4        2015-03-21 17:07:30
  5        2015-03-11 18:32:56
  6        2015-03-03 11:02:30

purchase_datedatetime64[ns] 列。我需要添加一个新列df[month],其中包含购买日期当月的第一天:

df['month']
2015-01-01
2015-02-01
2015-02-01
2015-03-01
2015-03-01
2015-03-01

我在 SQL 中寻找类似 @​​987654326@ 的东西。我试过以下代码:

     df['month']=df['purchase_date'].apply(lambda x : x.replace(day=1))

它以某种方式工作但返回:2015-01-01 14:05:21

【问题讨论】:

    标签: python pandas dataframe datetime64


    【解决方案1】:

    最简单、最快的方法是通过to_numpy 转换为numpy array,然后进行转换:

    df['month'] = df['purchase_date'].to_numpy().astype('datetime64[M]')
    print (df)
       user_id       purchase_date      month
    0        1 2015-01-23 14:05:21 2015-01-01
    1        2 2015-02-05 05:07:30 2015-02-01
    2        3 2015-02-18 17:08:51 2015-02-01
    3        4 2015-03-21 17:07:30 2015-03-01
    4        5 2015-03-11 18:32:56 2015-03-01
    5        6 2015-03-03 11:02:30 2015-03-01
    

    floorpd.offsets.MonthBegin(1) 的另一种解决方案,并在每月的第一天添加 pd.offsets.MonthEnd(0) 以获得正确的输出:

    df['month'] = (df['purchase_date'].dt.floor('d') + 
                               pd.offsets.MonthEnd(0) - pd.offsets.MonthBegin(1))
    print (df)
       user_id       purchase_date      month
    0        1 2015-01-23 14:05:21 2015-01-01
    1        2 2015-02-05 05:07:30 2015-02-01
    2        3 2015-02-18 17:08:51 2015-02-01
    3        4 2015-03-21 17:07:30 2015-03-01
    4        5 2015-03-11 18:32:56 2015-03-01
    5        6 2015-03-03 11:02:30 2015-03-01
    

    df['month'] = ((df['purchase_date'] + pd.offsets.MonthEnd(0) - pd.offsets.MonthBegin(1))
                             .dt.floor('d'))
    print (df)
       user_id       purchase_date      month
    0        1 2015-01-23 14:05:21 2015-01-01
    1        2 2015-02-05 05:07:30 2015-02-01
    2        3 2015-02-18 17:08:51 2015-02-01
    3        4 2015-03-21 17:07:30 2015-03-01
    4        5 2015-03-11 18:32:56 2015-03-01
    5        6 2015-03-03 11:02:30 2015-03-01
    

    最后的解决方案是由to_period 创建month period

    df['month'] = df['purchase_date'].dt.to_period('M')
    print (df)
       user_id       purchase_date   month
    0        1 2015-01-23 14:05:21 2015-01
    1        2 2015-02-05 05:07:30 2015-02
    2        3 2015-02-18 17:08:51 2015-02
    3        4 2015-03-21 17:07:30 2015-03
    4        5 2015-03-11 18:32:56 2015-03
    5        6 2015-03-03 11:02:30 2015-03
    

    ...然后通过to_timestampdatetimes,但速度有点慢:

    df['month'] = df['purchase_date'].dt.to_period('M').dt.to_timestamp()
    print (df)
       user_id       purchase_date      month
    0        1 2015-01-23 14:05:21 2015-01-01
    1        2 2015-02-05 05:07:30 2015-02-01
    2        3 2015-02-18 17:08:51 2015-02-01
    3        4 2015-03-21 17:07:30 2015-03-01
    4        5 2015-03-11 18:32:56 2015-03-01
    5        6 2015-03-03 11:02:30 2015-03-01
    

    有很多解决方案,所以:

    计时(在 pandas 1.2.3 中):

    rng = pd.date_range('1980-04-01 15:41:12', periods=100000, freq='20H')
    df = pd.DataFrame({'purchase_date': rng})  
    print (df.head())
    
    
    
    In [70]: %timeit df['purchase_date'].to_numpy().astype('datetime64[M]')
    8.6 ms ± 27.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    In [71]: %timeit df['purchase_date'].dt.floor('d') + pd.offsets.MonthEnd(n=0) - pd.offsets.MonthBegin(n=1)
    23 ms ± 130 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    In [72]: %timeit (df['purchase_date'] + pd.offsets.MonthEnd(0) - pd.offsets.MonthBegin(1)).dt.floor('d')
    23.6 ms ± 97.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    In [73]: %timeit df['purchase_date'].dt.to_period('M')
    9.25 ms ± 215 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    In [74]: %timeit df['purchase_date'].dt.to_period('M').dt.to_timestamp()
    17.6 ms ± 485 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    
    In [76]: %timeit df['purchase_date'] + pd.offsets.MonthEnd(0) - pd.offsets.MonthBegin(normalize=True)
    23.1 ms ± 116 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    In [77]: %timeit df['purchase_date'].dt.normalize().map(MonthBegin().rollback)
    1.66 s ± 7.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    【讨论】:

    • pd.offsets.MonthBegin(1) 的问题是当 purchase_date 是当月的第一天时,您的代码将返回上个月的第一天。
    • @pomber - 尝试使用pd.offsets.MonthBegin(0)
    • pd.offsets.MonthBegin(0) 在第一天工作,但在其余时间失败
    • 我同意,当日期是当月的第一天时,该解决方案存在缺陷。
    • 如何接受答案 - 它只是有缺陷,它完全忽略了极端情况(每月的第一天或最后一天,取决于MonthBegin 的参数)@jezrael,请扩展您的示例2015-01-012015-01-31看问题
    【解决方案2】:

    我们可以将date offsetSeries.dt.normalize结合使用:

    In [60]: df['month'] = df['purchase_date'].dt.normalize() - pd.offsets.MonthBegin(1)
    
    In [61]: df
    Out[61]:
       user_id       purchase_date      month
    0        1 2015-01-23 14:05:21 2015-01-01
    1        2 2015-02-05 05:07:30 2015-02-01
    2        3 2015-02-18 17:08:51 2015-02-01
    3        4 2015-03-21 17:07:30 2015-03-01
    4        5 2015-03-11 18:32:56 2015-03-01
    5        6 2015-03-03 11:02:30 2015-03-01
    

    much nicer solution from @BradSolomon

    In [95]: df['month'] = df['purchase_date'] - pd.offsets.MonthBegin(1, normalize=True)
    
    In [96]: df
    Out[96]:
       user_id       purchase_date      month
    0        1 2015-01-23 14:05:21 2015-01-01
    1        2 2015-02-05 05:07:30 2015-02-01
    2        3 2015-02-18 17:08:51 2015-02-01
    3        4 2015-03-21 17:07:30 2015-03-01
    4        5 2015-03-11 18:32:56 2015-03-01
    5        6 2015-03-03 11:02:30 2015-03-01
    

    【讨论】:

    • 或者传递normalize作为参数,df.purchase_date - pd.offsets.MonthBegin(1, normalize=True))
    • @BradSolomon,哇!谢谢!不知道pd.offsets.MonthBegin()有这样的参数
    • @MaxU 如果df['purchase_date'] 已经在一个月的第一天,减去MonthBegin(1) 将返回上个月的第一天。
    • 如果purchase_date已经是当月的第一天怎么办?对于2017-08-01 03:45:56,我得到:2017-07-01
    【解决方案3】:

    这个简单的解决方案怎么样?
    由于purchase_date 已经是datetime64[ns] 格式,您可以使用strftime 将日期格式化为始终包含月份的第一天。

    df['date'] = df['purchase_date'].apply(lambda x: x.strftime('%Y-%m-01'))
    
    print(df)
     user_id   purchase_date       date
    0   1   2015-01-23 14:05:21 2015-01-01
    1   2   2015-02-05 05:07:30 2015-02-01
    2   3   2015-02-18 17:08:51 2015-02-01
    3   4   2015-03-21 17:07:30 2015-03-01
    4   5   2015-03-11 18:32:56 2015-03-01
    5   6   2015-03-03 11:02:30 2015-03-01
    

    因为我们使用了strftime,所以现在date 列是object(字符串)类型:

    print(df.dtypes)
    user_id                   int64
    purchase_date    datetime64[ns]
    date                     object
    dtype: object
    

    现在,如果您希望它位于 datetime64[ns],只需使用 pd.to_datetime()

    df['date'] = pd.to_datetime(df['date'])
    
    print(df.dtypes)
    user_id                   int64
    purchase_date    datetime64[ns]
    date             datetime64[ns]
    dtype: object
    

    【讨论】:

      【解决方案4】:

      大多数建议的解决方案在当月的第一天都不起作用。

      以下解决方案适用于一个月中的任何一天:

      df['month'] = df['purchase_date'] + pd.offsets.MonthEnd(0) - pd.offsets.MonthBegin(normalize=True)
      

      [编辑]

      另一个更易读的解决方案是:

      from pandas.tseries.offsets import MonthBegin
      df['month'] = df['purchase_date'].dt.normalize().map(MonthBegin().rollback)
      

      注意不要使用:

      df['month'] = df['purchase_date'].map(MonthBegin(normalize=True).rollback)
      

      因为一个错误导致第一天的结果不正确:https://github.com/pandas-dev/pandas/issues/32616

      【讨论】:

      • 这也适用于每月的最后一天pd.to_datetime('2021-01-31')+pd.tseries.offsets.MonthEnd(0)-pd.tseries.offsets.MonthBegin() == Timestamp('2021-01-01 00:00:00')
      【解决方案5】:

      试试这个..

      df['month']=pd.to_datetime(df.purchase_date.astype(str).str[0:7]+'-01')
      
      Out[187]: 
         user_id        purchase_date       month
      0        1  2015-01-23 14:05:21  2015-01-01
      1        2  2015-02-05 05:07:30  2015-02-01
      2        3  2015-02-18 17:08:51  2015-02-01
      3        4  2015-03-21 17:07:30  2015-03-01
      4        5  2015-03-11 18:32:56  2015-03-01
      5        6  2015-03-03 11:02:30  2015-03-01
      

      【讨论】:

        【解决方案6】:

        要提取每个月的第一天,您可以编写一个小辅助函数如果提供的日期已经是一个月的第一天,它也可以工作。函数如下所示:

        def first_of_month(date):
            return date + pd.offsets.MonthEnd(-1) + pd.offsets.Day(1)
        

        你可以在pd.Seriesapply这个功能:

        df['month'] = df['purchase_date'].apply(first_of_month)
        

        这样您将获得month 列作为Timestamp。如果需要特定格式,可以使用strftime() 方法进行转换。

        df['month_str'] = df['month'].dt.strftime('%Y-%m-%d')
        

        【讨论】:

        • 这很好用,即使是在这个月的第一天。我把它矢量化了,而不是像这样.apply()df['date_flat'] = df['date'] + pd.offsets.MonthEnd(-1) + pd.offsets.Day(1)
        【解决方案7】:

        对我来说df['purchase_date'] - pd.offsets.MonthBegin(1) 没有用(每月的第一天失败),所以我像这样减去每月的天数:

        df['purchase_date'] - pd.to_timedelta(df['purchase_date'].dt.day - 1, unit='d')
        

        【讨论】:

        • 虽然这是唯一适用于当月第一天的解决方案,但由于某种原因它比使用 MonthBegin 慢得多,您能否加快速度?
        【解决方案8】:

        @Eyal:这就是我使用pd.offsets.MonthBegin 获取每月第一天并处理当天已经是每月第一天的情况。

        import datetime
        
        from_date= pd.to_datetime('2018-12-01')
        
        from_date = from_date - pd.offsets.MonthBegin(1, normalize=True) if not from_date.is_month_start else from_date
        
        from_date
        

        结果:Timestamp('2018-12-01 00:00:00')

        from_date= pd.to_datetime('2018-12-05')
        
        from_date = from_date - pd.offsets.MonthBegin(1, normalize=True) if not rom_date.is_month_start else from_date
        
        from_date
        

        结果:Timestamp('2018-12-01 00:00:00')

        【讨论】:

          【解决方案9】:

          试试这个 Pandas 库,其中 'purchase_date' 是放入模块的日期参数。

          date['month_start'] = pd.to_datetime(sched_slim.purchase_date)
          .dt.to_period('M')
          .dt.to_timestamp()
          

          【讨论】:

            猜你喜欢
            • 2019-06-06
            • 2016-07-27
            • 2017-07-08
            • 2020-06-24
            • 2020-06-02
            • 1970-01-01
            • 2019-11-12
            • 2023-03-08
            • 1970-01-01
            相关资源
            最近更新 更多