【问题标题】:Pandas groupby with multiple conditions具有多个条件的 Pandas groupby
【发布时间】:2022-01-23 18:51:36
【问题描述】:

我正在尝试创建通话记录摘要。 有4个案例

  1. 一个电话只有一个通话记录,并且有结果,我们 选择持续时间、状态和结果记录的值
  2. 同一部手机的多个通话记录有结果,我们选择通话记录的摘要、持续时间和结果记录,最长持续时间
  3. 一部电话只有一条通话记录,没有 结果,我们选择它的持续时间和状态值。结果记录将为无
  4. 同一部手机的多个通话记录没有结果,我们选择 通话记录的摘要和持续时间,最长持续时间。 结果记录将为无

我尝试的是循环组。但是在处理大量数据时速度非常慢。我想我需要使用熊猫方法而不是循环。如何使用 pandas 方法来实现相同的,具有多个条件。谢谢。

import pandas as pd
def get_summarized_call_logs_df(df):
    data_list = []
    phone_groups = df.groupby('phone')
    unique_phones = df.phone.unique()
    for ph in unique_phones:
        row_data = {"phone": ph}
        group = phone_groups.get_group(ph)
        group_len = len(group)
        if True in group['outcome'].to_list():
            outcome = group.loc[group['outcome'] == True]
            row_data.update({"has_outcome": True})
            if outcome.phone.count() == 1:
                # Cases where there is outcome for single calls
                row_data.update({"status": outcome.status.iloc[0],
                                 "duration": outcome.duration.iloc[0],
                                 "outcome_record": outcome.id.iloc[0]})
            else:
                # Cases where there is outcome for multiple calls
                # We choose the status and duration of outcome record with maximum duration
                out_rec = outcome.loc[outcome['duration'] == outcome['duration'].max()]
                row_data.update({"status": out_rec.status.iloc[0],
                                 "duration": out_rec.duration.iloc[0],
                                 "outcome_record": out_rec.id.iloc[0]})
        else:
            row_data.update({"has_outcome": False, "outcome_record": None})
            if group_len == 1:
                # Cases where there is no outcome for single calls
                row_data.update({"status": group.status.iloc[0], "duration": group.duration.iloc[0]})
            else:
                # Cases where there is no outcome for multiple calls
                # We choose the status and duration of the record with maximum duration
                row_data.update({"status": group.loc[group['duration'] == group['duration'].max()].status.iloc[0],
                                "duration": group.loc[group['duration'] == group['duration'].max()].duration.iloc[0]})
        data_list.append(row_data)
    new_df = pd.DataFrame(data_list)
    return new_df

if __name__ == "__main__":
    data = [
    {"id": 1, "phone": "123", "outcome": True, "status": "sale", "duration": 1550},
    {"id": 2, "phone": "123", "outcome": False, "status": "failed", "duration": 3},
    {"id": 3, "phone": "123", "outcome": False, "status": "no_ring", "duration": 5},
    {"id": 4, "phone": "456", "outcome": True, "status": "call_back", "duration": 550},
    {"id": 5, "phone": "456", "outcome": True, "status": "sale", "duration": 2500},
    {"id": 6, "phone": "456", "outcome": False, "status": "no_ring", "duration": 5},
    {"id": 7, "phone": "789", "outcome": False, "status": "no_pick", "duration": 4},
    {"id": 8, "phone": "741", "outcome": False, "status": "try_again", "duration": 25},
    {"id": 9, "phone": "741", "outcome": False, "status": "try_again", "duration": 10},
    {"id": 10, "phone": "741", "outcome": False, "status": "no_ring", "duration": 5},
    ]
    df = pd.DataFrame(data)
    new_df = get_summarized_call_logs_df(df)
    print(new_df)

它应该产生一个输出

  phone  has_outcome     status  duration  outcome_record
0   123         True       sale      1550             1.0
1   456         True       sale      2500             5.0
2   789        False    no_pick         4             NaN
3   741        False  try_again        25             NaN

【问题讨论】:

    标签: python pandas pandas-groupby


    【解决方案1】:

    我认为您可以简化逻辑。如果您主要按“结果”和“持续时间”对值进行排序,则只需删除重复项并保留每个排序组的最后一行,如下所示:

    cols = ['phone', 'outcome', 'duration']
    new_df = df.sort_values(cols).drop_duplicates('phone', keep='last')
    print(new_df)
    
    # Output:
       id phone  outcome     status  duration
    0   1   123     True       sale      1550
    4   5   456     True       sale      2500
    7   8   741    False  try_again        25
    6   7   789    False    no_pick         4
    

    来自@user10375196,得到预期的结果:

    new_df = new_df.rename(columns={'id': 'outcome_record', 'outcome': 'has_outcome'})
    new_df.loc[new_df.has_outcome == False, "outcome_record"] = None
    new_df.reset_index(drop=True, inplace=True)
    print(new_df)
    
    # Output:
       outcome_record phone  has_outcome     status  duration
    0             1.0   123         True       sale      1550
    1             5.0   456         True       sale      2500
    2             NaN   741        False  try_again        25
    3             NaN   789        False    no_pick         4
    

    【讨论】:

    • 应该是new_df = df.sort_values(cols).drop_duplicates('phone', keep='first') 而不是keep='last'?在处理真实数据时,我在 keep='last' 上得到错误的值,在 keep='first' 上得到更正...
    • 你确定吗?最高值位于数据框的底部,因为默认情况下顺序是升序的。 first: False ---> last: True, first: 3 ---> last: 1500
    • 对不起,我的错误。 “结果”字段的实际数据中有“无”值。而且我认为它给NoneTrue 更多的权重,当按升序排序时。在我的情况下,这导致了错误。我将None 替换为Falseoutcome 字段以解决此问题。顺便说一句,您的答案既简单又快捷。为了得到 'outcome_record' 并完成我使用的答案 'new_df = new_df.rename(columns={'id': 'outcome_record', 'outcome': 'has_outcome'}) new_df.loc[test_df.has_outcome == False, "结果_记录"] = 无 new_df.reset_index(drop=True, inplace=True)'
    • 我用你的代码更新了我的答案。
    【解决方案2】:

    只是提供一个替代流处理选项(无需将输入数据放入内存),基于convtools

    from convtools import conversion as c
    
    # fmt: off
    data = [
        {"id": 1, "phone": "123", "outcome": True, "status": "sale", "duration": 1550},
        {"id": 2, "phone": "123", "outcome": False, "status": "failed", "duration": 3},
        {"id": 3, "phone": "123", "outcome": False, "status": "no_ring", "duration": 5},
        {"id": 4, "phone": "456", "outcome": True, "status": "call_back", "duration": 550},
        {"id": 5, "phone": "456", "outcome": True, "status": "sale", "duration": 2500},
        {"id": 6, "phone": "456", "outcome": False, "status": "no_ring", "duration": 5},
        {"id": 7, "phone": "789", "outcome": False, "status": "no_pick", "duration": 4},
        {"id": 8, "phone": "741", "outcome": False, "status": "try_again", "duration": 25},
        {"id": 9, "phone": "741", "outcome": False, "status": "try_again", "duration": 10},
        {"id": 10, "phone": "741", "outcome": False, "status": "no_ring", "duration": 5},
    ]
    # fmt: on
    
    # you are interested in rows with max duration
    max_duration_call_log = c.ReduceFuncs.MaxRow(c.item("duration"))
    
    # you need to know whether there's been an outcome
    has_outcome = c.ReduceFuncs.Count(where=c.item("outcome")) > 0
    
    converter = (
        c.group_by(c.item("phone"))
        .aggregate(
            {
                "phone": c.item("phone"),
                "has_outcome": has_outcome,
                "status": max_duration_call_log.item("status"),
                "duration": max_duration_call_log.item("duration"),
                "outcome_record": c.if_(
                    has_outcome,
                    max_duration_call_log.item("id"),
                    None,
                ),
            }
        )
        # this step generates and compiles ad hoc function
        .gen_converter()
    )
    
    # fmt: off
    assert converter(data) == [
        {'phone': '123', 'has_outcome': True, 'status': 'sale', 'duration': 1550, 'outcome_record': 1},
        {'phone': '456', 'has_outcome': True, 'status': 'sale', 'duration': 2500, 'outcome_record': 5},
        {'phone': '789', 'has_outcome': False, 'status': 'no_pick', 'duration': 4, 'outcome_record': None},
        {'phone': '741', 'has_outcome': False, 'status': 'try_again', 'duration': 25, 'outcome_record': None},
    ]
    # fmt: on
    
    

    【讨论】:

    • 我还没有测试过这个。另一个答案对我来说似乎很简单
    • 以上是纯python,因此在某些情况下可能更灵活(例如MaxRow让你播放原始行)。此外,它不需要对数据进行预排序,它可以与流一起使用,因此不需要将数据放入内存中。然而,由于它是一个纯 python,它缺少 pandas 的矢量化和这些东西。因此,这可能是对 Polars/pandas 等其他工具的一个不错的补充。
    猜你喜欢
    • 2021-04-06
    • 1970-01-01
    • 1970-01-01
    • 2018-06-20
    • 2020-05-31
    • 2018-08-20
    • 2020-08-24
    • 2018-10-20
    • 1970-01-01
    相关资源
    最近更新 更多