9

I have two python dictionaries that I'm trying to sum the values together on. The answer in: Is there any pythonic way to combine two dicts (adding values for keys that appear in both)? gets me most of the way. However I have cases where the net values may be zero or negative but I still want the values in the final dictionary. Even though Counters will accept negative values, it will only output a value if it's greater than zero.

Example

from collections import Counter   
A = Counter({'a': 1, 'b': 2, 'c': -3, 'e': 5, 'f': 5})
B = Counter({'b': 3, 'c': 4, 'd': 5, 'e': -5, 'f': -6})
C = A + B
print(C.items())

Output: [('a', 1), ('c', 1), ('b', 5), ('d', 5)]

c = -3 + 4 = 1 is correct so the negative input is not an issue but e:0 and f:-1 are missing from the output

How can I perform the summation and get all values output?

Community
  • 1
  • 1
user3314562
  • 115
  • 1
  • 3

2 Answers2

13

Summing drops values at 0 and lower, yes, as documented:

Several mathematical operations are provided for combining Counter objects to produce multisets (counters that have counts greater than zero). Addition and subtraction combine counters by adding or subtracting the counts of corresponding elements. Intersection and union return the minimum and maximum of corresponding counts. Each operation can accept inputs with signed counts, but the output will exclude results with counts of zero or less.

[...]

  • The multiset methods are designed only for use cases with positive values. The inputs may be negative or zero, but only outputs with positive values are created. There are no type restrictions, but the value type needs to support addition, subtraction, and comparison.

You'll need to use Counter.update() if you wanted to retain the values at 0 or lower. Since this is an in-place operation you'll have to create a copy here:

>>> from collections import Counter   
>>> A = Counter({'a': 1, 'b': 2, 'c': -3, 'e': 5, 'f': 5})
>>> B = Counter({'b': 3, 'c': 4, 'd': 5, 'e': -5, 'f': -6})
>>> C = A.copy()
>>> C.update(B)
>>> C
Counter({'b': 5, 'd': 5, 'a': 1, 'c': 1, 'e': 0, 'f': -1})

If preserving A wasn't a goal, you can just update it directly.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 2
    Why does `Counter()` behave like this when summing negative counts? it feels quite unexpected to me, not the Python I know and admire... – Chris_Rands Mar 10 '17 at 16:23
  • 1
    @Chris_Rands because counters are multi sets; a set that both handles membership and a count for how many times it is part of the set. 0 (or lower) means it is no longer part of the set so is removed. – Martijn Pieters Mar 10 '17 at 16:52
  • Thanks, that makes sense. Next thought, in Python 3.5+ you can use can combine `dict`s with `{**A, **B}`, but that doesn't work with `Counter(**A,**B)` – Chris_Rands Mar 10 '17 at 17:28
  • 1
    @Chris_Rands: That works just fine, actually, but take into account that duplicate keys between `A` and `B` are not allowed when using that syntax. – Martijn Pieters Mar 10 '17 at 17:54
6

How about something like:

dict((x, a.get(x, 0) + b.get(x, 0)) for x in set(a)|set(b))

example:

>>> a = {'a':1, 'b':2, 'c':-3, 'e':5, 'f': 5}
>>> b = {'b':3, 'c':4, 'd':5, 'e':-5, 'f': -6}
>>>
>>> dict((x, a.get(x, 0) + b.get(x, 0)) for x in set(a)|set(b))
{'e': 0, 'a': 1, 'f': -1, 'd': 5, 'c': 1, 'b': 5}
Roope
  • 4,469
  • 2
  • 27
  • 51
  • 1
    This doesn't produce a new `Counter()` object, however. – Martijn Pieters Feb 21 '15 at 13:39
  • 3
    Was that required? To my understanding the objective was just to add two dictionaries together. But true, it doesn't. – Roope Feb 21 '15 at 13:41
  • specifically having a counter as the output isn't a requirement. In the end, I'm just outputting the final results to a file. This does work. The counters were just a means to add the dictionary I found in the referenced post. Thanks – user3314562 Feb 21 '15 at 13:53
  • Just a follow-up note. The processing time using the dictionary approach is dramatically slower than the counter approach. On the data sets I'm working with the difference was 1min using counters vs 10mins using dictionaries. These a multi-billion row tables being processed so if using the dictionary solution produces slow results try the alternate counter code below. – user3314562 Mar 26 '15 at 23:02