Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the most concise way in Python to group and sum a list of objects by the same property

Tags:

python

I have a list of objects of type C, where type C consists of properties X,Y,Z, e.g., c.X, c.Y, c.Z

Now I want to perform the following task:

  • Sum on the property Z of those objects that has the same value for property Y
  • Output a list of tuples (Y, sum of Zs with this Y)

What's the most concise way?

like image 705
KFL Avatar asked Jan 26 '12 01:01

KFL


People also ask

How do you sum a list of objects in Python?

Python provides an inbuilt function sum() which sums up the numbers in the list. Syntax: sum(iterable, start) iterable : iterable can be anything list , tuples or dictionaries , but most importantly it should be numbers. start : this start is added to the sum of numbers in the iterable.

How do you group values in a list in Python?

Use a list comprehension to group a list by values. Use the list comprehension syntax [list[1] for list in list_of_lists] to get a list containing only the second element from each list in list_of_lists . Call set(list) with list as the previous result to remove any duplicate elements from list .

How do you make groups in Python?

groupby() and pass the name of the column that you want to group on, which is "state" . Then, you use ["last_name"] to specify the columns on which you want to perform the actual aggregation. You can pass a lot more than just a single column name to . groupby() as the first argument.


1 Answers

The defaultdict approach is probably better, assuming c.Y is hashable, but here's another way:

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)]

To be a little more concrete about the differences:

  • This approach requires making a sorted copy of cs, which takes O(n log n) time and O(n) extra space. Alternatively, you can do cs.sort(key=get_y) to sort cs in-place, which doesn't need extra space but does modify the list cs. Note that groupby returns an iterator so there's not any extra overhead there. If the c.Y values aren't hashable, though, this does work, whereas the defaultdict approach will throw a TypeError.

    But watch out -- in recent Pythons it'll raise TypeError if there are any complex numbers in there, and maybe in other cases. It might be possible to make this work with an appropriate key function -- key=lambda e: (e.real, e.imag) if isinstance(e, complex) else e seems to be working for anything I've tried against it right now, though of course custom classes that override the __lt__ operator to raise an exception are still no go. Maybe you could define a more complicated key function that tests for this, and so on.

    Of course, all we care about here is that equal things are next to each other, not so much that it's actually sorted, and you could write an O(n^2) function to do that rather than sort if you so desired. Or a function that's O(num_hashable + num_nonhashable^2). Or you could write an O(n^2) / O(num_hashable + num_nonhashable^2) version of groupby that does the two together.

  • sblom's answer works for hashable c.Y attributes, with minimal extra space (because it computes the sums directly).

  • philhag's answer is basically the same as sblom's, but uses more auxiliary memory by making a list of each of the cs -- effectively doing what groupby does, but with hashing instead of assuming it's sorted and with actual lists instead of iterators.

So, if you know your c.Y attribute is hashable and only need the sums, use sblom's; if you know it's hashable but want them grouped for something else as well, use philhag's; if they might not be hashable, use this one (with extra worrying as noted if they might be complex or a custom type that overrides __lt__).

like image 175
Danica Avatar answered Sep 18 '22 04:09

Danica