【问题标题】:Best way to group two dataframes in pandas and perform operation on matching groups在熊猫中对两个数据框进行分组并对匹配组执行操作的最佳方法
【发布时间】:2020-04-06 11:52:57
【问题描述】:

给定两个数据帧,我想将它们按一些列分组,并为具有相同组键的每对组执行一些操作,在此过程中修改第一个数据帧。应该有左加入组,即。如果第二个数据帧中不在第一个数据帧中的组应该被忽略。每个操作都会为第一个数据帧计算一些额外的列。

关于我的案例的一些细节 - 我在电视广告领域工作,必须为每个数据点计算一些指标,但这些指标只有在将记录分组到 channel, visitor_id, date 的存储桶后才有意义。

性能很重要 - 如果可能的话,我希望避免在内存中制作多个副本,因为这些数据集非常庞大 - 多达 2000 万条记录。

我想一个例子会更清楚:

import pandas as pd

first = pd.DataFrame([
    {'visitor_id': 123, 'channel': 'Test1', 'duration': 1},
    {'visitor_id': 123, 'channel': 'Test1', 'duration': 5},
    {'visitor_id': 123, 'channel': 'Test1', 'duration': 7},
    {'visitor_id': 123, 'channel': 'Test2', 'duration': 2},
    {'visitor_id': 234, 'channel': 'Test1', 'duration': 3},
    {'visitor_id': 234, 'channel': 'Test2', 'duration': 4},
    {'visitor_id': 234, 'channel': None, 'duration': 4},
])

second = pd.DataFrame([
    {'visitor_id': 123, 'channel': 'Test1', 'price': 100},
    {'visitor_id': 123, 'channel': 'Test2', 'price': 200},
    {'visitor_id': 567, 'channel': 'Test2', 'price': 100},
    {'visitor_id': 234, 'channel': 'Test1', 'price': 100},
    {'visitor_id': 234, 'channel': 'Test2', 'price': 300},
    {'visitor_id': 234, 'channel': None, 'price': 400},
])
# function I want to apply to each group, in reality it's more complex
# and need to access group records directly
def add_cost_per_second(first_group, second_group):
    first_group['cost_per_second'] = second_group['price'].sum() / first_group['duration']
    return first_group

我想将add_cost_per_second 应用到每对组,修改过程中的第一个组。


我尝试过使用 groupby 和 get_group,但它不适用于 NaN 值 - 它们会自动删除。也许我可以事先手动检查它?

无论如何,我现在使用的解决方案是这样的:

def apply_for_groups(df1: pd.DataFrame, df2: pd.DataFrame, callback, *groups_names, **kwargs) -> pd.DataFrame:
    # we can't use groupby, because it's dropping groups with np.nan inside
    # probably can be further optimized, but query is already considered pretty fast
    # https://jakevdp.github.io/PythonDataScienceHandbook/03.12-performance-eval-and-query.html
    result = []
    unique = df1.drop_duplicates(groups_names)
    for group_keys in unique[list(groups_names)].itertuples(index=False):
        q = " & ".join(
            f"({name} == '{key}')" if not pd.isna(key) else f"({name}.isna())"
            for name, key in zip(groups_names, group_keys)
        )
        # can we get rid of that .copy()?
        result.append(callback(df1.query(q).copy(), df2.query(q).copy(), **kwargs))
    # is there a better way? Like, send original slice to callback and modify it
    # instead of returning dataframes and concatenating them?
    return pd.concat(result)
# usage
result = apply_for_groups(first, second, add_cost_per_second, 'visitor_id', 'channel')

有没有更好的方法按列对两个数据帧进行分组并将它们压缩在一起?

【问题讨论】:

  • 示例不包括日期列,您说它是组的一部分。此外,如果您在给出示例数据的情况下显示所需的输出,那么您要完成的工作会更清楚。
  • 我想简化一个例子,所以我使用了两个组而不是三个。如果它具有误导性,我可以删除对“日期”列的提及,并且我会尝试调整示例以使其更加清晰。基本上,我正在寻找将两个数据帧分组在同一列上并从两者中获取相等键的值的最快方法。

标签: python pandas dataframe pandas-groupby


【解决方案1】:

试试df.join。您希望第二个数据框中的索引与第一个数据框中的列名匹配,或者将相同的列用于两个数据框中的索引。如果 visitor_id 和 channel 的组合是唯一的,并且您将 NaN 替换为一个值(此处为“无频道”),这应该可以工作。

first['channel'] = first['channel'].fillna('No channel') 
second['channel'] = second['channel'].fillna('No channel') 
first.join(second.set_index(['visitor_id', 'channel'], how='left', on=['visitor_id', 'channel'])

   visitor_id     channel  duration  price
0         123       Test1         1    100
1         123       Test1         5    100
2         123       Test1         7    100
3         123       Test2         2    200
4         234       Test1         3    100
5         234       Test2         4    300
6         234  No channel         4    400

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    • 2018-06-30
    • 2018-04-20
    • 2021-01-15
    • 2018-02-07
    • 2019-08-20
    • 2017-12-13
    相关资源
    最近更新 更多