首页 > 解决方案 > Python分组;仅在满足条件时保留

问题描述

假设您有一个数据集,其中包含零件、项目、报价、价格和isSelected.

对于每个零件、项目和报价,如果有isSelected,则只保留那一行,但如果没有isSelected,则保留该零件、项目和报价组合的所有行。

请参见下面的示例。

数据集:

部分 项目 引用 价格 被选中
1 一个 1 5.0
1 一个 1 2.2 是的
5 C 2 6.6
5 C 2 1.2 是的
3 3 5.5
3 3 4.6

期望的结果:

部分 项目 引用 价格 被选中
1 一个 1 2.2 是的
5 C 2 1.2 是的
3 3 5.5
3 3 4.6

标签: pythonpandasgroup-bypandas-groupby

解决方案


这种一般的任务类别可以通过遍历由 a或的操作GroupBy产生的对象来解决。.groupbySeriesDataFrame

在这种特殊情况下,您还可以使用GroupBy.apply方法,该方法对每个组执行计算并将结果连接在一起。

GroupBy该类的文档在这里

我将首先介绍循环版本,因为对于尚未熟悉计算的“DataFrame 风格”的程序员来说,它可能更易于使用。但是,我建议.apply尽可能使用该版本。处理大型数据集时速度会更快,并且可能会消耗更少的内存。它也被认为是更“惯用”的风格,它将迫使您学习如何将代码分解为单独的函数。

使用循环

很多人没有意识到DataFrame.groupbyGroupBy对象)的结果可以被迭代。此处记录了此特定功能。

除此之外,逻辑由一个简单的if语句、一些 Pandas 子集和concat函数组成。

完整示例:

import io
import pandas as pd

data = pd.read_csv(io.StringIO('''
Part,Project,Quote,Price,isSelected
1,A,1,5.0,No
1,A,1,2.2,Yes
5,C,2,6.6,No
5,C,2,1.2,Yes
3,B,3,5.5,No
3,B,3,4.6,No
'''))

group_results = []
for _, group in data.groupby(['Part', 'Project', 'Quote']):
    is_selected = group['isSelected'] == 'Yes'

    if is_selected.any():
        # Select the rows where 'isSelected' is True, and
        # then select the first row from that output.
        # Using [0] instead of 0 ensures that the result
        # is still a DataFrame, and that it does not get
        # "squeezed" down to a Series.
        group_result = group.loc[is_selected].iloc[[0]]

    else:
        group_result = group

    group_results.append(group_result)

results = pd.concat(group_results)
print(results)

输出:

   Part Project  Quote  Price isSelected
1     1      A       1    2.2        Yes
4     3      B       3    5.5         No
5     3      B       3    4.6         No
3     5      C       2    1.2        Yes

使用.apply

GroupBy.apply方法本质上pd.concat为您完成了列表附加部分。我们没有编写循环,而是编写了一个函数,我们将其传递给.apply

import io
import pandas as pd

data = pd.read_csv(io.StringIO('''
Part,Project,Quote,Price,isSelected
1,A,1,5.0,No
1,A,1,2.2,Yes
5,C,2,6.6,No
5,C,2,1.2,Yes
3,B,3,5.5,No
3,B,3,4.6,No
'''))


groups = data.groupby(['Part', 'Project', 'Quote'], as_index=False)


def process_group(group):
    is_selected = group['isSelected'] == 'Yes'

    if is_selected.any():
        # Select the rows where 'isSelected' is True, and
        # then select the first row from that output.
        # Using [0] instead of 0 ensures that the result
        # is still a DataFrame, and that it does not get
        # "squeezed" down to a Series.
        group_result = group.loc[is_selected].iloc[[0]]

    else:
        group_result = group

    return group_result


# Use .reset_index to remove the extra index layer created by Pandas,
# which is not necessary in this situation.
results = groups.apply(process_group).reset_index(level=0, drop=True)
print(results)

输出:

   Part Project  Quote  Price isSelected
1     1       A      1    2.2        Yes
4     3       B      3    5.5         No
5     3       B      3    4.6         No
3     5       C      2    1.2        Yes

推荐阅读