分组/汇总数据的方法?

Ara*_*Fey 6 python grouping list

我有一些数据存储在列表中,我想根据值进行分组.

例如,如果我的数据是

data = [(1, 'a'), (2, 'x'), (1, 'b')]
Run Code Online (Sandbox Code Playgroud)

我希望按每个元组中的第一个值对其进行分组

result = [(1, 'ab'), (2, 'x')]
Run Code Online (Sandbox Code Playgroud)

我该怎么办呢?

更一般地说,在python中分组数据的推荐方法是什么?有没有可以帮助我的食谱?

Ara*_*Fey 7

用于各种分组的首选数据结构是dict.我们的想法是使用唯一标识组的内容作为dict的键,并将属于同一组的所有值存储在同一个键下.

例如,您的数据可以存储在这样的字典中:

{1: ['a', 'b'],
 2: ['x']}
Run Code Online (Sandbox Code Playgroud)

您用于对值进行分组的整数用作dict键,值将在列表中聚合.

我们使用dict的原因是因为它可以在恒定的O(1)时间内将键映射到值.这使得分组过程非常有效并且也非常容易.对于所有类型的分组任务,代码的一般结构将始终相同:您迭代数据并逐渐填充具有分组值的dict.使用a defaultdict而不是常规dict使整个过程更加容易,因为我们不必担心用空列表初始化dict.

import collections

groupdict = collections.defaultdict(list)
for value in data:
    group = value[0]
    value = value[1]
    groupdict[group].append(value)

# result:
# {1: ['a', 'b'],
#  2: ['x']}
Run Code Online (Sandbox Code Playgroud)

将数据分组后,剩下的就是将dict转换为所需的输出格式:

result = [(key, ''.join(values)) for key, values in groupdict.items()]
# result: [(1, 'ab'), (2, 'x')]
Run Code Online (Sandbox Code Playgroud)

分组食谱

以下部分将提供不同类型的输入和输出的配方,并显示如何按各种事物分组.一切的基础是以下片段:

import collections

groupdict = collections.defaultdict(list)
for value in data:  # input
    group = ???  # group identifier
    value = ???  # value to add to the group
    groupdict[group].append(value)

result = groupdict  # output
Run Code Online (Sandbox Code Playgroud)

每个注释行都可以/必须根据您的使用情况进行自定义.

输入

输入数据的格式决定了如何迭代它.

在本节中,我们将自定义for value in data:配方行.

分组

在这里,我们将介绍从数据中提取组标识符的各种方法.

在本节中,我们将自定义group = ???配方行.

修改聚合值

有时您会想要修改您正在分组的值.例如,如果您将元组分组为第一个元素(1, 'a')和(1, 'b')第一个元素,则可能需要从每个元组中删除第一个元素以获得结果,{1: ['a', 'b']}而不是{1: [(1, 'a'), (1, 'b')]}.

在本节中,我们将自定义value = ???配方行.

  • 没变

    如果您不想以任何方式更改值,只需value = ???从代码中删除该行.

  • 只保留一个list/tuple/dict元素

    如果您的值是列表[1, 'a'],您只想保留'a':

    value = value[1]
    
    Run Code Online (Sandbox Code Playgroud)

    或者,如果他们是喜欢的{'firstname': 'foo', 'lastname': 'bar'},你只想保留名字:

    value = value['firstname']
    
    Run Code Online (Sandbox Code Playgroud)
  • 删除第一个列表/元组元素

    如果您的值是像[1, 'a', 'foo']和的列表,[1, 'b', 'bar']并且您想要丢弃每个元组的第一个元素以获得类似的组[['a', 'foo], ['b', 'bar']],请使用切片语法:

    value = value[1:]
    
    Run Code Online (Sandbox Code Playgroud)
  • 删除/保留任意列表/元组/字典元素

    如果您的值是像['foo', 'bar', 'baz']或类似的列表,{'firstname': 'foo', 'middlename': 'bar', 'lastname': 'baz'}并且您想要删除或仅保留其中一些元素,请首先创建一组要保留或删除的元素.例如:

    indices_to_keep = {0, 2}
    keys_to_delete = {'firstname', 'middlename'}
    
    Run Code Online (Sandbox Code Playgroud)

    然后从此列表中选择相应的代码段:

    1. 保留列表元素: value = [val for i, val in enumerate(value) if i in indices_to_keep]
    2. 要删除列表元素: value = [val for i, val in enumerate(value) if i not in indices_to_delete]
    3. 要保持dict元素: value = {key: val for key, val in value.items() if key in keys_to_keep]
    4. 要删除dict元素: value = {key: val for key, val in value.items() if key not in keys_to_delete]

产量

分组完成后,我们就会填写一份defaultdict清单.但是期望的结果并不总是(默认)dict.

在本节中,我们将自定义result = groupdict配方行.

  • 一个普通的词典

    要将defaultdict转换为常规字典,只需dict在其上调用构造函数:

    result = dict(groupdict)
    
    Run Code Online (Sandbox Code Playgroud)
  • 列表(group, value)对

    要从[(group1, value1), (group1, value2), (group2, value3)]dict 获得结果{group1: [value1, value2], group2: [value3]},请使用列表推导:

    result = [(group, value) for group, values in groupdict.items()
                               for value in values]
    
    Run Code Online (Sandbox Code Playgroud)
  • 仅包含值的嵌套列表

    要从[[value1, value2], [value3]]dict 获得结果{group1: [value1, value2], group2: [value3]},请使用dict.values:

    result = list(groupdict.values())
    
    Run Code Online (Sandbox Code Playgroud)
  • 只是值的平面列表

    要获得类似于[value1, value2, value3]dict 的结果{group1: [value1, value2], group2: [value3]},请使用列表理解来展平dict :

    result = [value for values in groupdict.values() for value in values]
    
    Run Code Online (Sandbox Code Playgroud)
  • 展平可迭代的值

    如果您的值是列表或其他迭代类似的

    groupdict = {group1: [[list1_value1, list1_value2], [list2_value1]]}
    
    Run Code Online (Sandbox Code Playgroud)

    你想要一个扁平化的结果

    result = {group1: [list1_value1, list1_value2, list2_value1]}
    
    Run Code Online (Sandbox Code Playgroud)

    你有两个选择:

    1. 用dict理解来展平列表:

      result = {group: [x for iterable in values for x in iterable]
                                for group, values in groupdict.items()}
      
      Run Code Online (Sandbox Code Playgroud)
    2. 首先避免使用list.extend而不是创建可迭代列表list.append.换句话说,改变

      groupdict[group].append(value)
      
      Run Code Online (Sandbox Code Playgroud)

      至

      groupdict[group].extend(value)
      
      Run Code Online (Sandbox Code Playgroud)

      然后就开始吧result = groupdict.

  • 排序列表

    Dicts是无序数据结构.如果迭代dict,你永远不知道它的元素将以哪种顺序列出.如果您不关心订单,可以使用上面显示的配方.但是,如果你做有关订单的照顾,你要输出相应的排序.

    我将使用以下dict来演示如何以各种方式对输出进行排序:

    groupdict = {'abc': [1], 'xy': [2, 5]}
    
    Run Code Online (Sandbox Code Playgroud)

    请记住,这是一个元组合,可能需要与此答案的其他部分结合才能获得您想要的输出.一般的想法是在使用字典键从dict中提取值之前对字典键进行排序:

    groups = sorted(groupdict.keys())
    # groups = ['abc', 'xy']
    
    Run Code Online (Sandbox Code Playgroud)

    请记住,sorted如果要自定义排序顺序,则接受关键功能.例如,如果dict键是字符串,并且您希望按长度对它们进行排序:

    groups = sorted(groupdict.keys(), key=len)
    # groups = ['xy', 'abc']
    
    Run Code Online (Sandbox Code Playgroud)

    对密钥进行排序后,使用它们以正确的顺序从dict中提取值:

    # groups = ['abc', 'xy']
    result = [groupdict[group] for group in groups]
    # result = [[1], [2, 5]]
    
    Run Code Online (Sandbox Code Playgroud)

    请记住,这可以与此答案的其他部分结合使用,以获得不同类型的输出.例如,如果要保留组标识符:

    # groups = ['abc', 'xy']
    result = [(group, groupdict[group]) for group in groups]
    # result = [('abc', [1]), ('xy', [2, 5])]
    
    Run Code Online (Sandbox Code Playgroud)

    为方便起见,以下是一些常用的排序顺序:

    1. 按每组值的数量排序:

       groups = sorted(groudict.keys(), key=lambda group: len(groupdict[group]))
       result = [groupdict[group] for group in groups]
       # result = [[2, 5], [1]]
      
      Run Code Online (Sandbox Code Playgroud)
  • 计算每个组中的值的数量

    要计算与每个组关联的元素数,请使用以下len函数:

    result = {group: len(values) for group, values in groupdict.items()}
    
    Run Code Online (Sandbox Code Playgroud)

    如果要计算不同元素的数量,请使用set以消除重复:

    result = {group: len(set(values)) for group, values in groupdict.items()}
    
    Run Code Online (Sandbox Code Playgroud)

一个例子

为了演示如何将此配方中的工作解决方案拼凑在一起,让我们尝试转换输入

data = [["A",0], ["B",1], ["C",0], ["D",2], ["E",2]]
Run Code Online (Sandbox Code Playgroud)

成

result = [["A", "C"], ["B"], ["D", "E"]]
Run Code Online (Sandbox Code Playgroud)

换句话说,我们按照第二个元素对列表进行分组.

配方的前两行总是相同的,所以让我们先复制一下:

import collections

groupdict = collections.defaultdict(list)
Run Code Online (Sandbox Code Playgroud)

现在我们必须找出如何循环输入.由于我们的输入是一个简单的值列表,因此正常for循环就足够了:

for value in data:
Run Code Online (Sandbox Code Playgroud)

接下来,我们必须从值中提取组标识符.我们按第二个列表元素进行分组,因此我们使用索引:

    group = value[1]
Run Code Online (Sandbox Code Playgroud)

下一步是转换价值.由于我们只想保留每个列表的第一个元素,我们再次使用列表索引:

    value = value[0]
Run Code Online (Sandbox Code Playgroud)

最后,我们必须弄清楚如何将我们生成的字典转换为列表.我们想要的是一个没有组的值列表.我们查阅配方的输出部分以找到合适的dict展平片段:

result = list(groupdict.values())
Run Code Online (Sandbox Code Playgroud)

Etvoilà:

data = [["A",0], ["B",1], ["C",0], ["D",2], ["E",2]]

import collections

groupdict = collections.defaultdict(list)
for value in data:
    group = value[1]
    value = value[0]
    groupdict[group].append(value)

result = list(groupdict.values())
# result: [["A", "C"], ["B"], ["D", "E"]]
Run Code Online (Sandbox Code Playgroud)