【问题标题】:How to join pandas dataframe to itself by condition?如何按条件将熊猫数据框加入自身?
【发布时间】:2021-08-29 07:22:50
【问题描述】:

我有一个带有 2 个相关列“日期”和“值”的 python pandas 数据框,我们假设它看起来像这样并按日期排序:

data = pd.DataFrame({"date": ["2021-01-01", "2021-01-31", "2021-02-01", "2021-02-28", "2021-03-01", "2021-03-31", "2021-04-01", "2021-04-02"],
                     "value": [1,2,3,4,5,6,5,8]})
data["date"] = pd.to_datetime(data['date'])

现在我想以这样一种方式将 dataFrame 加入到自身中,即我得到每个月最后一个可用天的值更高的下一个可用天。在我们的示例中,这基本上应该如下所示:

date, value, date2, value2:
2021-01-31, 2, 2021-02-01, 3
2021-02-28, 4, 2021-03-01, 5
2021-03-31, 6, 2021-04-02, 8
2021-04-02, 8, NaN, NaN

我目前对该问题的部分解决方案如下所示:

last_days = data.groupby([data.date.dt.year, data.date.dt.month]).last()
res = [data.loc[(data.date>date) & (data.value > value)][:1] for date, value in zip(last_days.date, last_days.value)]
print(res)

但是因为这个答案"Don't iterate over rows in a dataframe",我觉得这不像是熊猫的方式。

那么问题来了,怎么用 pandas 的方式解决呢?

【问题讨论】:

    标签: python pandas dataframe join


    【解决方案1】:

    如果您没有太多行,您可以生成所有项目对并从那里过滤。

    让我们从获取该月的最后几天开始:

    >>> last = data.loc[data['date'].dt.daysinmonth == data['date'].dt.day]
    >>> last
            date  value
    1 2021-01-31      2
    3 2021-02-28      4
    5 2021-03-31      6
    

    现在使用cross 连接将每个最后一天映射到任何可能的日期,然后根据更晚日期和更大值等条件进行过滤:

    >>> pairs = pd.merge(last, data, how='cross', suffixes=('', '2'))
    >>> pairs = pairs.loc[pairs['date2'].gt(pairs['date']) & pairs['value2'].gt(pairs['value'])]
    >>> pairs
             date  value      date2  value2
    2  2021-01-31      2 2021-02-01       3
    3  2021-01-31      2 2021-02-28       4
    4  2021-01-31      2 2021-03-01       5
    5  2021-01-31      2 2021-03-31       6
    6  2021-01-31      2 2021-04-01       5
    7  2021-01-31      2 2021-04-02       8
    12 2021-02-28      4 2021-03-01       5
    13 2021-02-28      4 2021-03-31       6
    14 2021-02-28      4 2021-04-01       5
    15 2021-02-28      4 2021-04-02       8
    23 2021-03-31      6 2021-04-02       8
    

    最后用GroupBy.idxmin()得到第一个date2

    >>> pairs.loc[pairs.groupby(['date', 'value'])['value2'].idxmin().values]
             date  value      date2  value2
    2  2021-01-31      2 2021-02-01       3
    12 2021-02-28      4 2021-03-01       5
    23 2021-03-31      6 2021-04-02       8
    

    否则你可能想要apply,这与迭代行完全一样。

    【讨论】:

    • 过滤后的帧看起来不像预期的输出。
    • 好的,有 2 项改进,但这看起来是个不错的解决方案,谢谢! 1)将“last”编辑成“merge”:pd.merge(last, data, ...),否则出错。 2)你最后的面具,不是我想要的,但也许我不够精确,对不起。我正在处理工作日,因此数据中一个月的最后一个条目不一定是该月的最后一天(例如,当月的最后一天是星期日时)。但我的面具正在工作。如果你修复 1) 我会接受你的回答。
    • 附加问题:这对我来说更像是一种熊猫(/sql/数据库)方法,我认为这就是我想要的,再次感谢。但更好吗?为什么? (我还在学习熊猫)我想它需要更多的内存(与循环/列表理解相比),更快吗?它更具可读性吗? ...?
    • @jackattack 修复了它!关于它是否更好:这一切都取决于现实世界的用例。你是对的,一般来说你不想迭代行,但如果你有很多项目,它可能比生成所有对更快,而且肯定会占用更少的内存。如果您只是想探索 pandas,那么看看这两个选项以及它们的工作原理会很有趣。
    【解决方案2】:

    首先创建 2 个蒙版:一个用于月底,另一个用于下个月的第一天。

    m1 = data['date'].diff(1).shift(-1) == pd.Timedelta(days=1)
    m2 = m1.shift(1, fill_value=False)
    

    最后,将忽略索引的 2 个结果连接起来:

    >>> pd.concat([data.loc[m1].reset_index(drop=True),
                   data.loc[m2].reset_index(drop=True)], axis="columns")
    
            date  value       date  value
    0 2021-01-31      2 2021-02-01      3
    1 2021-02-28      4 2021-03-01      5
    2 2021-03-31      6 2021-04-01      5
    3 2021-04-01      5 2021-04-02      8
    

    【讨论】:

    • 我一开始也是这么想的。您和我一样错过了“价值更高的下一个可用日期”部分。 '2021-03-21' 不应与 '2021-04-01' 配对,因为值 5 不大于 6。
    • 是的,正如@HenryEcker 已经写的那样,不幸的是,这不是一个解决方案。我们这里有两个问题: 1. m1 没有给我们数据框中每个月的最后可用日期。如果我正确解释它,它会为我们提供与下一个条目的差异为 1 天的条目。 2.下一个条目的价值不一定更大。
    猜你喜欢
    • 2017-10-20
    • 2019-12-21
    • 2017-05-08
    • 1970-01-01
    • 2013-12-20
    • 2022-07-07
    • 2019-05-15
    • 1970-01-01
    • 2019-05-02
    相关资源
    最近更新 更多