Python中最简洁的方法是使用同一属性对对象列表进行分组和求和

时间:2012-01-26 01:22:03

标签: python

我有一个C类型的对象列表,其中类型C由属性X,Y,Z组成,例如c.X,c.Y,c.Z

现在我想执行以下任务:

  • 对属性Y具有相同值的那些对象的属性Z求和
  • 输出元组列表(Y,Z与此Y的总和)

什么是最简洁的方式?

6 个答案:

答案 0 :(得分:8)

from collections import defaultdict
totals = defaultdict(int)
for c in cs:
  totals[c.Y] += c.Z

tuples = totals.items()

答案 1 :(得分:8)

defaultdict方法可能更好,假设c.Y可以播放,但这是另一种方式:

from itertools import groupby
from operator import attrgetter
get_y = attrgetter('Y')
tuples = [(y, sum(c.Z for c in cs_with_y) for y, cs_with_y in 
           groupby(sorted(cs, key=get_y), get_y)]

对差异稍微具体一点:

  • 此方法需要制作cs的排序副本,该副本需要O(n log n)时间和O(n)额外空间。或者,您可以cs.sort(key=get_y)对就地cs进行排序,这不需要额外的空间,但会修改列表cs。请注意,groupby返回一个迭代器,因此没有任何额外的开销。如果c.Y值不是hashable,那么这确实有效,而defaultdict方法会抛出TypeError

    但请注意 - 在最近的Pythons中,如果那里有任何复杂的数字,它可能会提升TypeError,也许在其他情况下。有可能使用适当的key函数进行此工作 - key=lambda e: (e.real, e.imag) if isinstance(e, complex) else e似乎适用于我现在尝试过的任何操作,当然,自定义类会覆盖{{1 }}运算符引发异常仍然没有去。也许你可以定义一个更复杂的关键函数来测试它,等等。

    当然,我们所关心的只是相同的东西是彼此相邻的,而不是它实际上是排序的,你可以写一个O(n ^ 2)函数来做到这一点而不是排序如果你这样做期望。或者是O(num_hashable + num_nonhashable ^ 2)的函数。或者你可以编写一个O(n ^ 2)/ O(num_hashable + num_nonhashable ^ 2)版本的__lt__来完成这两个版本。

  • sblom's answer适用于可散列groupby属性,具有最小的额外空间(因为它直接计算总和)。

  • philhag's answer与sblom基本相同,但通过列出每个c.Y的列表来使用更多辅助内存 - 有效地执行c所做的事情,但是使用散列而不是假设它已经排序并使用实际列表而不是迭代器。

因此,如果你知道你的groupby属性是可以清除的并且只需要总和,那么使用sblom's;如果你知道它是可以清洗的,但是希望它们也能用其他东西分组,那就用philhag了;如果它们可能不是可以清洗的,请使用这个(如果它们可能很复杂或者自定义类型覆盖c.Y,则需要额外担心)。

答案 2 :(得分:6)

您可以使用collections.defaultdict按y值对列表进行分组,然后对其z值求和:

import collections
ymap = collections.defaultdict(list)
for c in listOfCs:
  ymap[c.Y].append(c)
print ([(y, sum(c.Z for c in clist)) for y,clist in ymap.values()])

答案 3 :(得分:3)

使用pandas可能类似于:

df.groupby('Y')['Z'].sum()

实施例

>>> import pandas
>>> df = pandas.DataFrame(dict(X=[1,2,3], Y=[1,-1,1], Z=[3,4,5]))
>>> df
   X  Y   Z
0  1  1   3
1  2  -1  4
2  3  1   5
>>> df.groupby('Y')['Z'].sum()
Y
-1    4
1     8
>>> 

答案 4 :(得分:0)

您可以使用Counter

from collections import Counter
cnt = Counter()
for c in cs:
  cnt[c.Y] += c.Z


print cnt

答案 5 :(得分:0)

尝试下面的运行示例:

import collections
class Product():
    def __init__(self, name, amount, group = None):
        self.name = name
        self.amount = amount
        self.group = group

    def __repr__(self):
        return "%s%r" % (self.__class__, self.__dict__)

    def __str__(self):
        return "%r" % self.__dict__
def print_amount_by_group():
    product_list = [Product('p1', 11, 'GRP3'), Product('p3', 11, 'GRP2'), 
        Product('p2', 11, 'GRP2'), Product('p25', 11)]
    prod_dict = collections.defaultdict(list)
    for c in product_list:
        prod_dict[c.group].append(c)
    print([(key, sum(p.amount for p in product)) for key, product in         
    prod_dict.items()])
print_ammount_by_group()

结果: [('GRP3',11),('GRP2',22),(None,11)]