リスト内の数値の累積合計を見つける方法は?


92
time_interval = [4, 6, 12]

[4, 4+6, 4+6+12]リストを取得するために、のように数値を合計したいと思いますt = [4, 10, 22]

私は以下を試しました:

t1 = time_interval[0]
t2 = time_interval[1] + t1
t3 = time_interval[2] + t2
print(t1, t2, t3)  # -> 4 10 22

回答:


128

このような配列で多くの数値作業を行っている場合numpyは、累積合計関数が付属していることをお勧めしますcumsum

import numpy as np

a = [4,6,12]

np.cumsum(a)
#array([4, 10, 22])

Numpyは、この種のことに関して、純粋なpythonよりも高速であることがよくあります。@ Ashwiniaccumuと比較してください:

In [136]: timeit list(accumu(range(1000)))
10000 loops, best of 3: 161 us per loop

In [137]: timeit list(accumu(xrange(1000)))
10000 loops, best of 3: 147 us per loop

In [138]: timeit np.cumsum(np.arange(1000))
100000 loops, best of 3: 10.1 us per loop

しかしもちろん、それがnumpyを使用する唯一の場所である場合、それに依存する価値はないかもしれません。


3
これにはnp.cumsun、変換時間を考慮して、リストで始まるケースが必要です。
hpaulj 2016

3
良い点@ hpaulj、aから始める(または目指す)人listにはお勧めしませんnumpy
askewchan 2016

私はnumpyのが最速だとは思わないstackoverflow.com/questions/15889131/...
Chris_Rands

3
上で述べたように、同意しました。あなたや@hpauljのような反応を避けることが、私の答えの最初と最後の行でその範囲を制限しようとした理由です:-/
askewchan 2016

1
@alex:を使用してtimeit-n指定されていない場合、合計時間が少なくとも0.2秒になるまで、10の累乗を連続して試行することにより、適切なループ数が計算されます。」それが違いを生むことを期待するならば、あなたは-n 1000それらをすべて同等にするために供給することができます。
askewchan

94

Python 2では、次のように独自のジェネレーター関数を定義できます。

def accumu(lis):
    total = 0
    for x in lis:
        total += x
        yield total

In [4]: list(accumu([4,6,12]))
Out[4]: [4, 10, 22]

また、Python 3.2以降では、次のものを使用できますitertools.accumulate()

In [1]: lis = [4,6,12]

In [2]: from itertools import accumulate

In [3]: list(accumulate(lis))
Out[3]: [4, 10, 22]

5
PEP 572-代入式(Python 3.8で期待される)は興味深い代替案を示していtotal = 0; partial_sums = [total := total + v for v in values]ます。私はまだaccumulateもっと速いと期待しています。
Steven Rumbalski 2018

3
@StevenRumbalski男、私は個人的にそれが史上最悪のPEPだと思います。バート十分な...
アシュビニーChaudharyさん

19

見よ:

a = [4, 6, 12]
reduce(lambda c, x: c + [c[-1] + x], a, [0])[1:]

出力します(期待どおり):

[4, 10, 22]

17
効率的ではありませんc + [c[-1] + x]何度も実行するための総費用は、入力長の2次の合計ランタイムになります。
user2357112は、2017

reduceは、1回限りの累積合計には適していますが、cumsum関数を何度も呼び出す場合は、ジェネレーターを使用して、cumulative_sum値を「前処理」し、後続の呼び出しごとにO(1)でそれらにアクセスできます。
スコットスカイルズ2017

17

Python 3.4で上位2つの回答のベンチマークを実行したところ、多くの状況itertools.accumulateよりも高速でありnumpy.cumsum、多くの場合はるかに高速であることがわかりました。ただし、コメントからわかるように、これが常に当てはまるとは限らず、すべてのオプションを徹底的に調査することは困難です。(さらに興味深いベンチマーク結果がある場合は、コメントを追加するか、この投稿を編集してください。)

いくつかのタイミング...

短いリストのaccumulate場合、約4倍高速です。

from timeit import timeit

def sum1(l):
    from itertools import accumulate
    return list(accumulate(l))

def sum2(l):
    from numpy import cumsum
    return list(cumsum(l))

l = [1, 2, 3, 4, 5]

timeit(lambda: sum1(l), number=100000)
# 0.4243644131347537
timeit(lambda: sum2(l), number=100000)
# 1.7077815784141421

より長いリストのaccumulate場合、約3倍高速です。

l = [1, 2, 3, 4, 5]*1000
timeit(lambda: sum1(l), number=100000)
# 19.174508565105498
timeit(lambda: sum2(l), number=100000)
# 61.871223849244416

場合numpy arrayにキャストされていないlistaccumulateより速く2回程度はまだです。

from timeit import timeit

def sum1(l):
    from itertools import accumulate
    return list(accumulate(l))

def sum2(l):
    from numpy import cumsum
    return cumsum(l)

l = [1, 2, 3, 4, 5]*1000

print(timeit(lambda: sum1(l), number=100000))
# 19.18597290944308
print(timeit(lambda: sum2(l), number=100000))
# 37.759664884768426

インポートを2つの関数の外に置いても、を返す場合はnumpy arrayaccumulate2倍近く高速です。

from timeit import timeit
from itertools import accumulate
from numpy import cumsum

def sum1(l):
    return list(accumulate(l))

def sum2(l):
    return cumsum(l)

l = [1, 2, 3, 4, 5]*1000

timeit(lambda: sum1(l), number=100000)
# 19.042188624851406
timeit(lambda: sum2(l), number=100000)
# 35.17324400227517

10
特にチケットの購入やセキュリティスクリーニングなど、飛行機が電車よりも速く町を移動することは期待できません。同様にlist、特にarray見返りを受け入れたくない場合は、numpyを使用して5つのアイテムのうちの1つを処理することはありません。問題のリストが本当に非常に短い場合、それらの実行時間は重要ではありません---依存関係と読みやすさが確かに支配的です。しかしlist、かなりの長さの均一な数値データ型を広く使用するのはばかげています。そのため、numpyのはarray 考え、適切な、通常は速くなり。
askewchan 2016

@askewchanええと、私はこれを短いリストで見つけるだけではありません。OPの質問では、numpy配列ではなく出力としてリストを要求しています。おそらく、それぞれの使用が適切な場合に、より明確になるように回答を編集できます:)
Chris_Rands 2016

@askewchan実際、私はもっと詳細な比較で答えを編集しました。どんな状況でもnumpy、何かを見落としていなければ、もっと速くなると思いますか?
Chris_Rands 2016

2
ああ、そうです、確かに:)何かを見落としているとは言えませんが、入力と出力を考慮せずに単独で比較することは困難です。sum2関数内のほとんどの時間は、おそらくl配列への変換です。タイミングa = np.array(l)np.cumsum(a)個別に試してください。次に、a = np.tile(np.arange(1, 6), 1000)vsを試してくださいl = [1,2,3,4,5]*1000。他の数値プロセス(l最初の場所での作成やロードなど)を実行するプログラムでは、作業データはおそらくすでに配列に含まれており、作成には一定のコストがかかります。
askewchan 2016

1
@askewchan私はあなたと同じ考えを持っていたので、a = np.array(l)の時間を計りました。リストへの変換がなく、入力としてnumpy配列があるsum2の場合、長いリスト/配列の場合、sum2は私のコンピューターのsum1よりも5倍高速です。
満州2017年

9

これを試してください:累積関数は、演算子addとともに、実行中の加算を実行します。

import itertools  
import operator  
result = itertools.accumulate([1,2,3,4,5], operator.add)  
list(result)

5
operator.addとにかくデフォルトの操作は加算なので、合格する必要はありません。
EugeneYarmash19年


5

単純なforループを使用して、線形時間で累積合計リストを計算できます。

def csum(lst):
    s = lst.copy()
    for i in range(1, len(s)):
        s[i] += s[i-1]
    return s

time_interval = [4, 6, 12]
print(csum(time_interval))  # [4, 10, 22]

標準ライブラリitertools.accumulateは(Cで実装されているため)より高速な代替手段になる可能性があります。

from itertools import accumulate
time_interval = [4, 6, 12]
print(list(accumulate(time_interval)))  # [4, 10, 22]

2
values = [4, 6, 12]
total  = 0
sums   = []

for v in values:
  total = total + v
  sums.append(total)

print 'Values: ', values
print 'Sums:   ', sums

このコードを実行すると、

Values: [4, 6, 12]
Sums:   [4, 10, 22]

2

Python3では、ith要素が元のリストの最初のi + 1要素の合計であるリストの累積合計を見つけるには、次のようにします。

a = [4 , 6 , 12]
b = []
for i in range(0,len(a)):
    b.append(sum(a[:i+1]))
print(b)

または、リスト内包表記を使用することもできます。

b = [sum(a[:x+1]) for x in range(0,len(a))]

出力

[4,10,22]

これは正しいように見えますが、ドキュメントへのリンクを削除できます。それがないと、私は賛成できません。
S Meaden

2

numpyを2.7で動作させずにpythonicの方法が必要な場合は、これが私の方法です。

l = [1,2,3,4]
_d={-1:0}
cumsum=[_d.setdefault(idx, _d[idx-1]+item) for idx,item in enumerate(l)]

それでは、試してみて、他のすべての実装に対してテストしてみましょう。

import timeit, sys
L=list(range(10000))
if sys.version_info >= (3, 0):
    reduce = functools.reduce
    xrange = range


def sum1(l):
    cumsum=[]
    total = 0
    for v in l:
        total += v
        cumsum.append(total)
    return cumsum


def sum2(l):
    import numpy as np
    return list(np.cumsum(l))

def sum3(l):
    return [sum(l[:i+1]) for i in xrange(len(l))]

def sum4(l):
    return reduce(lambda c, x: c + [c[-1] + x], l, [0])[1:]

def this_implementation(l):
    _d={-1:0}
    return [_d.setdefault(idx, _d[idx-1]+item) for idx,item in enumerate(l)]


# sanity check
sum1(L)==sum2(L)==sum3(L)==sum4(L)==this_implementation(L)
>>> True    

# PERFORMANCE TEST
timeit.timeit('sum1(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.001018061637878418

timeit.timeit('sum2(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.000829620361328125

timeit.timeit('sum3(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.4606760001182556 

timeit.timeit('sum4(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.18932826995849608

timeit.timeit('this_implementation(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.002348129749298096

2

リストの長さとパフォーマンスによっては、これに対する多くの答えがあります。パフォーマンスを考えずに考えることができる非常に簡単な方法の1つは、次のとおりです。

a = [1, 2, 3, 4]
a = [sum(a[0:x:1]) for x in range(len(a)+1)][1:]
print(a)

[1, 3, 6, 10]

これはリスト内包表記を使用することによるものであり、これはかなりうまくいく可能性があります。ここでサブアレイを何度も追加しているだけです。これを即興で簡単にすることができます。

あなたの努力に乾杯!


1

まず、サブシーケンスの実行リストが必要です。

subseqs = (seq[:i] for i in range(1, len(seq)+1))

次にsum、各サブシーケンスを呼び出すだけです。

sums = [sum(subseq) for subseq in subseqs]

(すべてのプレフィックスを繰り返し追加するため、これは最も効率的な方法ではありません。ただし、ほとんどのユースケースではおそらく問題ではなく、考える必要がない場合は理解しやすくなります。累計。)

Python 3.2以降を使用itertools.accumulateしている場合は、次の方法で使用できます。

sums = itertools.accumulate(seq)

また、3.1以前を使用している場合は、「同等の」ソースをドキュメントから直接コピーできます(2.5以前のに変更next(it)するit.next()場合を除く)。


9
これは2次時間で実行されます(OPにとっては問題ではないかもしれませんが、言及する価値があります)。
クリス・テイラー

まず、N = 3の場合、誰が2次時間を気にしますか?そして、私はそれが過度に複雑だとは思いません。これは2つの非常に単純なステップであり、それぞれが1つのイテレーターを別のイテレーターに変換し、英語の説明を直接翻訳します。(彼がシリーズを定義する珍しい方法を使用しているという事実は、長さ0のプレフィックスがカウントされないため、少し複雑になります...しかし、それは問題に固有のものであり、それをに入れる方が良いと思いました。最後にrange行うことによってそれをハックし[1:]たり、無視したりするよりも。)
abarnert 2013

1
おそらく、OPの実際の問題は[4,6,12]、質問で書いたように、それが何であるかをすでに知っているので、の部分的な合計を取得することではありません!
クリス・テイラー

@ChrisTaylor:彼は、これを書く方法をすでに知っているが、「もっと簡単な書き方」を望んでいると明確に述べました。
abarnert 2013

1

これを試して:

result = []
acc = 0
for i in time_interval:
    acc += i
    result.append(acc)

-1
In [42]: a = [4, 6, 12]

In [43]: [sum(a[:i+1]) for i in xrange(len(a))]
Out[43]: [4, 10, 22]

これは、小さなリストの場合、@ Ashwiniによる上記のジェネレーターメソッドよりもわずかに高速です

In [48]: %timeit list(accumu([4,6,12]))
  100000 loops, best of 3: 2.63 us per loop

In [49]: %timeit [sum(a[:i+1]) for i in xrange(len(a))]
  100000 loops, best of 3: 2.46 us per loop

より大きなリストの場合、ジェネレーターは確実に実行する方法です。。。

In [50]: a = range(1000)

In [51]: %timeit [sum(a[:i+1]) for i in xrange(len(a))]
  100 loops, best of 3: 6.04 ms per loop

In [52]: %timeit list(accumu(a))
  10000 loops, best of 3: 162 us per loop

1
たった3つのアイテムリストのタイミングです。10^ 4のアイテムを試してください。
Ashwini Chaudhary 2013

1
確かに、リストが大きい場合、ジェネレーターははるかに高速です。
reptilicus 2013

-1

ややハッキーですが、機能しているようです:

def cumulative_sum(l):
  y = [0]
  def inc(n):
    y[0] += n
    return y[0]
  return [inc(x) for x in l]

内側の関数はy外側の字句スコープで宣言されたものを変更できると思いましたが、それは機能しなかったので、代わりに構造を変更して厄介なハックを行います。ジェネレーターを使用する方がおそらくよりエレガントです。


-1

Numpyを使用せずに、配列を直接ループして、途中で合計を累積できます。例えば:

a=range(10)
i=1
while((i>0) & (i<10)):
    a[i]=a[i-1]+a[i]
    i=i+1
print a

結果:

[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

-1

累積合計の純粋なPythonワンライナー:

cumsum = lambda X: X[:1] + cumsum([X[0]+X[1]] + X[2:]) if X[1:] else X

これは、再帰的な累積合計に触発された再帰的なバージョンです。いくつかの説明:

  1. 最初の用語X[:1]は前の要素を含むリストであり、ほとんど同じです[X[0]](空のリストに対して文句を言う)です。
  2. cumsum2項の再帰呼び出しは、現在の要素[1]と残りのリストを処理し、その長さは1つ減ります。
  3. if X[1:]の方が短いですif len(X)>1

テスト:

cumsum([4,6,12])
#[4, 10, 22]

cumsum([])
#[]

そして累積積のシミュレーション:

cumprod = lambda X: X[:1] + cumprod([X[0]*X[1]] + X[2:]) if X[1:] else X

テスト:

cumprod([4,6,12])
#[4, 24, 288]

-1
l = [1,-1,3]
cum_list = l

def sum_list(input_list):
    index = 1
    for i in input_list[1:]:
        cum_list[index] = i + input_list[index-1]
        index = index + 1 
    return cum_list

print(sum_list(l))

-1

ここに別の楽しい解決策があります。これはlocals()、内包表記、つまりリスト内包範囲内で生成されたローカル変数を利用します。

>>> [locals().setdefault(i, (elem + locals().get(i-1, 0))) for i, elem 
     in enumerate(time_interval)]
[4, 10, 22]

locals()各反復の外観は次のとおりです。

>>> [[locals().setdefault(i, (elem + locals().get(i-1, 0))), locals().copy()][1] 
     for i, elem in enumerate(time_interval)]
[{'.0': <enumerate at 0x21f21f7fc80>, 'i': 0, 'elem': 4, 0: 4},
 {'.0': <enumerate at 0x21f21f7fc80>, 'i': 1, 'elem': 6, 0: 4, 1: 10},
 {'.0': <enumerate at 0x21f21f7fc80>, 'i': 2, 'elem': 12, 0: 4, 1: 10, 2: 22}]

小さなリストのパフォーマンスはひどいものではありません。

>>> %timeit list(accumulate([4, 6, 12]))
387 ns ± 7.53 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

>>> %timeit np.cumsum([4, 6, 12])
5.31 µs ± 67.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

>>> %timeit [locals().setdefault(i, (e + locals().get(i-1,0))) for i,e in enumerate(time_interval)]
1.57 µs ± 12 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

そして明らかに、より大きなリストでは横ばいになります。

>>> l = list(range(1_000_000))
>>> %timeit list(accumulate(l))
95.1 ms ± 5.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit np.cumsum(l)
79.3 ms ± 1.07 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit np.cumsum(l).tolist()
120 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit [locals().setdefault(i, (e + locals().get(i-1, 0))) for i, e in enumerate(l)]
660 ms ± 5.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

この方法は醜くて実用的ではありませんが、確かに楽しいです。


-2
lst = [4,6,12]

[sum(lst[:i+1]) for i in xrange(len(lst))]

より効率的なソリューション(より大きなリスト?)を探している場合は、ジェネレーターを使用numpyすることをお勧めします(または、パフォーマンスが本当に気になる場合は使用してください)。

def gen(lst):
    acu = 0
    for num in lst:
        yield num + acu
        acu += num

print list(gen([4, 6, 12]))

-3

これはHaskellスタイルになります:

def wrand(vtlg):

    def helpf(lalt,lneu): 

        if not lalt==[]:
            return helpf(lalt[1::],[lalt[0]+lneu[0]]+lneu)
        else:
            lneu.reverse()
            return lneu[1:]        

    return helpf(vtlg,[0])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.