【问题标题】:Comparing date column values in one dateframe with two date column in another dataframe by row in Pandas在 Pandas 中逐行比较一个数据框中的日期列值与另一个数据框中的两个日期列
【发布时间】:2020-01-08 23:05:47
【问题描述】:

我有一个这样的数据框,其中包含两个日期列和一个数量列:

     start_date       end_date          qty
1    2018-01-01      2018-01-08         23
2    2018-01-08      2018-01-15         21           
3    2018-01-15      2018-01-22         5
4    2018-01-22      2018-01-29         12

我有第二个数据框,其中仅包含几年的年假,如下所示:

         holiday
1       2018-01-01 
2       2018-01-27
3       2018-12-25
4       2018-12-26

如果第二个数据帧中的日期介于第一个日期帧的日期值之间,我想逐行浏览第一个数据帧并将布尔值分配给新列假期。结果如下所示:

  start_date       end_date          qty         holidays
1    2018-01-01      2018-01-08         23       True
2    2018-01-08      2018-01-15         21       False  
3    2018-01-15      2018-01-22         5        False
4    2018-01-22      2018-01-29         12       True

当我尝试使用 for 循环执行此操作时,出现以下错误:

ValueError: 只能比较标签相同的 Series 对象

我们将不胜感激。

【问题讨论】:

  • 请分享整个错误信息,以及所有相关代码和数据。请参阅:How to Askminimal reproducible example。奇怪的是你不需要循环,你读过 Pandas 文档吗?
  • 最后一个元素不应该是假的吗?

标签: python pandas datetime


【解决方案1】:

如果您想要一个完全矢量化的解决方案,请考虑使用底层 numpy 数组:

import numpy as np


def holiday_arr(start, end, holidays):
    start = start.reshape((-1, 1))
    end = end.reshape((-1, 1))
    holidays = holidays.reshape((1, -1))
    result = np.any(
        (start <= holiday) & (holiday <= end),
        axis=1
    )
    return result

如果您有上述数据框(称它们为df1df2),您可以通过运行获得所需的结果:

df1["contains_holiday"] = holiday_arr(
    df1["start_date"].to_numpy(),
    df1["end_date"].to_numpy(),
    df2["holiday"].to_numpy()
)

df1 然后看起来像:

  start_date   end_date  qty  contains_holiday
1 2018-01-01 2018-01-08   23              True
2 2018-01-08 2018-01-15   21             False
3 2018-01-15 2018-01-22    5             False
4 2018-01-22 2018-01-29   12              True

【讨论】:

  • 像魅力一样工作。我希望我能给你买一瓶啤酒……或者二十:D
【解决方案2】:

尝试:

def _is_holiday(row, df2):
    return ((df2['holiday'] >= row['start_date']) & (df2['holiday'] <= row['end_date'])).any()

df1.apply(lambda x: _is_holiday(x, df2), axis=1)

【讨论】:

    【解决方案3】:

    我不确定您为什么要逐行进行。但是布尔比较会更快。

    df['holiday'] = ((df2.holiday >= df.start_date) &  (df2.holiday <= df.end_date))
    

    时间

    >>> 1000 loops, best of 3: 1.05 ms per loop
    

    引用@hchw 解决方案(逐行)

    def _is_holiday(row, df2):
        return ((df2['holiday'] >= row['start_date']) & (df2['holiday'] <= row['end_date'])).any()
    
    df.apply(lambda x: _is_holiday(x, df2), axis=1)
    
    >>> The slowest run took 4.89 times longer than the fastest. This could mean that an intermediate result is being cached.
    100 loops, best of 3: 4.46 ms per loop
    

    【讨论】:

    • 我之前试过了,得到以下错误:ValueError: Can only compare same-labeled Series objects
    • 您是说第一个解决方案行不通吗?它应该可以正常工作(只要您将这些列作为正确的日期时间列 - 如果不使用 pd.to_datetime 并转换它们)。
    • 是的,第一个解决方案不起作用,由于我对熊猫的了解有限,我不知道。
    • 查看start_date / end_date / holiday 列是否都是日期时间列。
    • 我查过,都是日期时间
    【解决方案4】:

    尝试IntervalIndex.contains 与列表理解和np.sum

    iix = pd.IntervalIndex.from_arrays(df1.start_date, df1.end_date, closed='both')
    df1['holidays'] = np.sum([iix.contains(x) for x in df2.holiday], axis=0) >= 1
    
    Out[812]:
      start_date   end_date  qty  holidays
    1 2018-01-01 2018-01-08   23      True
    2 2018-01-08 2018-01-15   21     False
    3 2018-01-15 2018-01-22    5     False
    4 2018-01-22 2018-01-29   12      True
    

    注意:我假设 start_dateend_dateholiday 列是日期时间格式。如果不是,则需要在运行上述命令之前将它们转换如下

    df1.start_date = pd.to_datetime(df1.start_date)
    df1.end_date = pd.to_datetime(df1.end_date)
    df2.holiday = pd.to_datetime(df2.holiday)
    

    【讨论】:

    • 这是唯一至少可以做某事的解决方案。它将所有假期都设置为真实。是的,日期都是 datetime64。
    • 此解决方案将df1 的每一行与df2 的所有行作为您的描述和所需的输出进行检查。因此,如果df1 中的每一行都包含df2 中的任何holiday,则所有行都将返回True。它在样本数据上正常工作,所以我怀疑是这种情况。仔细检查df1 中的每一行与df2 中的所有holiday
    猜你喜欢
    • 2020-09-13
    • 2022-01-01
    • 2023-04-10
    • 2022-08-10
    • 1970-01-01
    • 2019-09-01
    • 2020-02-10
    • 2018-03-14
    • 2020-04-11
    相关资源
    最近更新 更多