Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cumulative addition in a list based on an indices list

Say I have list, list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]

And another list, ind_list = [0, 4, 7]

I want to create a third list that will contain the cumulative sum of the first list which "resets" on every index from ind_list.

To clarify, the result should be res_list = [100, 105, 106, 108, 200, 203, 204, 300, 306, 312]

like image 895
Zennie Avatar asked Nov 07 '18 14:11

Zennie


People also ask

How do you do a cumulative sum list in Python?

We declare an empty list cum_list to which we will append elements to form the cumulative sum list. Initialize a sum variable sm=0. Start iterating over the input list, with each iteration we increment the sum value to previous value+ the current element. On each iteration, the sum value is appended to the cum_list.

What is meant by cumulative sum?

Cumulative sums, or running totals, are used to display the total sum of data as it grows with time (or any other series or progression). This lets you view the total contribution so far of a given measure against time.


8 Answers

Use the following:

cs= np.cumsum(list_a)
for i in ind_list:
    if i==0:
        continue
    cs[i:]-=cs[i-1]

Result:

cs
>>array([100, 105, 106, 108, 200, 203, 204, 300, 306, 312])
like image 164
Binyamin Even Avatar answered Nov 10 '22 00:11

Binyamin Even


Some itertools to the rescue:

from itertools import accumulate as acc, chain

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

list(chain(*(acc(list_a[x:y]) for x, y in zip(ind_list, ind_list[1:]+[None]))))
# [100, 105, 106, 108, 200, 203, 204, 300, 306, 312]
like image 20
user2390182 Avatar answered Nov 10 '22 01:11

user2390182


It's NumPy tagged and finding vectorized solutions is fun, so here's one -

def intervaled_cumsum(list_a, ind_list):
    a = np.array(list_a)
    a[ind_list[1:]] -= np.add.reduceat(a,ind_list)[:-1]
    return a.cumsum()

Sample run -

In [54]: list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]

In [55]: ind_list = [0, 4, 7]

In [56]: intervaled_cumsum(list_a, ind_list)
Out[56]: array([100, 105, 106, 108, 200, 203, 204, 300, 306, 312])
like image 21
Divakar Avatar answered Nov 09 '22 23:11

Divakar


Or just create a generator:

def generate_running_sum(summands, index_list):
    current_sum = 0
    for i, summand in enumerate(summands):
        if i in set(index_list):
            current_sum = 0
        current_sum += summand
        yield current_sum

Applied to your data:

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

res_list = list(generate_running_sum(summands=list_a, index_list=ind_list))
print(res_list)
like image 43
hiro protagonist Avatar answered Nov 09 '22 23:11

hiro protagonist


I had to expand the ind_list manually:

ind_list = [0, 4, 7] + [len(list_a)]

Just running a nested loop with the nested range set to the ind_list above:

count, final = 0, []
for i in range(len(ind_list)-1):
    count = 0
    for i in range(ind_list[i],ind_list[i+1]):
        count += list_a[i]
        final.append(count)
like image 31
petruz Avatar answered Nov 09 '22 23:11

petruz


Maximum itertools abuse!

Setup

from itertools import accumulate, chain, islice, tee

def pairwise(iterable):
    'pairwise recipe from itertools docs'
    it1, it2 = tee(iterable)
    next(it2)
    return zip(it1, it2)

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

Solution

ind_list.append(None)
result = list(chain.from_iterable(accumulate(islice(list_a, start, stop))
              for start, stop in pairwise(ind_list)))

print(result)

Output

[100, 105, 106, 108, 200, 203, 204, 300, 306, 312]

The idea behind using itertools facilities whenever possible here is to avoid creating intermediary list-slices which unnecessarily consume memory.

~edit~~

Memory efficient Python 2.7 solution

from itertools import chain, islice, izip, tee

def pairwise(iterable):
    'pairwise recipe from itertools docs'
    it1, it2 = tee(iterable)
    next(it2)
    return izip(it1, it2)

def my_cumsum(iterable):
    s = 0
    for x in iterable:
        s += x
        yield s

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

ind_list.append(None)
result = list(chain.from_iterable(my_cumsum(islice(list_a, start, stop))
              for start, stop in pairwise(ind_list)))

print(result)
like image 26
timgeb Avatar answered Nov 10 '22 01:11

timgeb


I did it with a combination of zip and np.cumsum.

I considered indexes from [0-4),[4-7) and [7-(len(list))).

And then I found the cumulative sum of each slice of the list and then put it in another list:

import numpy as np

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

def createcumsum(list_a, ind_list):
    # For adding the end of the list to the indexes if it doesn't exist
    if ind_list[-1] != len(list_a):
        ind_list.append(len(list_a))
    res_list=[]
    # Considers the indexes from 0-4, 4-7, 7-(endoflist)
    for x,y in zip(ind_list, ind_list[1:]):
        # Take cumulativesum on the above mentioned slices
        res_list.extend(np.cumsum(list_a[x:y]))
    return res_list
print(createcumsum(list_a, ind_list))

Output

[100, 105, 106, 108, 200, 203, 204, 300, 306, 312]
like image 44
Albin Paul Avatar answered Nov 10 '22 01:11

Albin Paul


A slightly simpler answer only using NumPy:

import numpy as np
list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [2, 4, 7]
res_list = []

for i, n in enumerate(ind_list):
    if i == 0:
        n_prev=0
    else:
        n_prev = ind_list[i-1]
    for k in range(n-n_prev):
        res_list.append(np.sum(list_a[n_prev:n_prev+k+1]))
    if i == len(ind_list)-1:
        for k in range(len(list_a)-n):
            res_list.append(np.sum(list_a[n:n+k+1]))

print(res_list)

Output

[100, 105, 106, 108, 200, 203, 204, 300, 306, 312]
like image 33
b-fg Avatar answered Nov 10 '22 00:11

b-fg