統計:Pythonでの組み合わせ


122

Pythonで組み合わせ(nCr)を計算する必要がありますが、それを行うための関数mathnumpyまたはstat ライブラリを見つけることができません。タイプの関数のようなもの:

comb = calculate_combinations(n, r)

実際の組み合わせではなく、可能な組み合わせの数が必要なので、itertools.combinations興味がありません。

最後に、組み合わせを計算する数値が大きくなりすぎて、階乗が巨大になるため、階乗の使用を避けたいと思います。

これは本当に答えやすい質問のようですが、実際のすべての組み合わせを生成することについての質問に溺れています。

回答:


121

scipy.special.comb(scipyの古いバージョンではscipy.misc.comb)を参照してください。exactがFalseの場合、関数gammalnを使用して、時間をかけずに精度を高めます。正確な場合は、任意精度の整数を返します。計算に時間がかかる場合があります。


5
scipy.misc.combscipy.special.combバージョンを支持して廃止されました0.10.0
Dilawar、2017

120

自分で書いてみませんか?それはワンライナーなどです:

from operator import mul    # or mul=lambda x,y:x*y
from fractions import Fraction

def nCk(n,k): 
  return int( reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1) )

テスト-パスカルの三角形の印刷:

>>> for n in range(17):
...     print ' '.join('%5d'%nCk(n,k) for k in range(n+1)).center(100)
...     
                                                   1                                                
                                                1     1                                             
                                             1     2     1                                          
                                          1     3     3     1                                       
                                       1     4     6     4     1                                    
                                    1     5    10    10     5     1                                 
                                 1     6    15    20    15     6     1                              
                              1     7    21    35    35    21     7     1                           
                           1     8    28    56    70    56    28     8     1                        
                        1     9    36    84   126   126    84    36     9     1                     
                     1    10    45   120   210   252   210   120    45    10     1                  
                  1    11    55   165   330   462   462   330   165    55    11     1               
               1    12    66   220   495   792   924   792   495   220    66    12     1            
            1    13    78   286   715  1287  1716  1716  1287   715   286    78    13     1         
         1    14    91   364  1001  2002  3003  3432  3003  2002  1001   364    91    14     1      
      1    15   105   455  1365  3003  5005  6435  6435  5005  3003  1365   455   105    15     1   
    1    16   120   560  1820  4368  8008 11440 12870 11440  8008  4368  1820   560   120    16     1
>>> 

PS。置換int(round(reduce(mul, (float(n-i)/(i+1) for i in range(k)), 1))) するint(reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1))ように編集されているため、大きなN / Kでもエラーになりません


26
+1は、シンプルなものを書くことを提案するため、reduceを使用するため、およびパスカル三角形を使用したクールなデモのため
jon_darkstar

6
この回答が間違っているため、-1:print factorial(54)/(factorial(54-27))/ factorial(27)== nCk(54、27)はFalseを返します。
ロバートキング2013

3
@robertking-さて、あなたはささいなことと技術的に正しいことの両方でした。私がやったことは、自分の関数を書く方法を説明するためのものでした。浮動小数点の精度のため、NとKが十分に大きい場合は正確ではないことがわかっていました。しかし、私たちはそれを修正することができます-上記を参照してください、大きな数字でもエラーにならないはずです
Nas Banov

9
これはおそらくHaskellでは高速ですが、残念ながらPythonではできません。他の多くの回答、たとえば@Alex Martelli、JF Sebastian、および私自身の回答と比較すると、実際にはかなり遅いです。
Todd Owen

9
Python 3の場合、私もする必要がありましたfrom functools import reduce
Velizar Hristov 2016

52

グーグルコードのクイック検索はそれを与えます(それは@Mark Byersの答えからの式を使用します):

def choose(n, k):
    """
    A fast way to calculate binomial coefficients by Andrew Dalke (contrib).
    """
    if 0 <= k <= n:
        ntok = 1
        ktok = 1
        for t in xrange(1, min(k, n - k) + 1):
            ntok *= n
            ktok *= t
            n -= 1
        return ntok // ktok
    else:
        return 0

choose()scipy.misc.comb()正確な答えが必要な場合よりも10倍高速です(すべての0 <=(n、k)<1e3ペアでテスト)。

def comb(N,k): # from scipy.comb(), but MODIFIED!
    if (k > N) or (N < 0) or (k < 0):
        return 0L
    N,k = map(long,(N,k))
    top = N
    val = 1L
    while (top > (N-k)):
        val *= top
        top -= 1
    n = 1L
    while (n < k+1L):
        val /= n
        n += 1
    return val

パッケージを必要としない素晴らしいソリューション
Edward Newell


このchoose関数にはもっと多くの投票が必要です!Python 3.8にはmath.combがありますが、チャレンジのためにPython 3.6を使用する必要があり、非常に大きな整数に対して正確な結果が得られる実装はありませんでした。これは高速で実行されます。
11:13に偵察

42

あなたは、正確な結果たい場合速度を、してみてくださいgmpy - gmpy.combあなたが求める正確に何をすべきか、すべきである、それはかなり速い(もちろんのだ、とgmpyの原作者、私が偏っ;-)。


6
確かに、gmpy2.comb()10倍の速さよりchoose():コードのための私の答えからのどちらかであるかのPython 3にfor k, n in itertools.combinations(range(1000), 2): f(n,k)f()gmpy2.comb()choose()
JFS

あなたはパッケージの作成者なので、適切な場所を指すようにリンク切れ修正します
。–

@ SeldomNeedy、code.google.comへのリンクは1つの正しい場所です(サイトは現在アーカイブモードです)。もちろんそれはgithubの場所、見つけるのは簡単だ、そこからgithub.com/aleaxit/gmpy、とは、PyPI 1、pypi.python.org/pypi/gmpy2を!) -それは両方にリンクして、
アレックスマルテッリ

@AlexMartelli混乱してすみません。JavaScriptが(選択的に)無効にされている場合、ページには404が表示されます。それは、不正なAIがアーカイブされたGoogle Code Projectソースを非常に簡単に組み込むことを思いとどまらせるためだと思いますか?
SeldomNeedy 2016

28

正確な結果が必要な場合は、を使用してくださいsympy.binomial。最速の方法のようです。

x = 1000000
y = 234050

%timeit scipy.misc.comb(x, y, exact=True)
1 loops, best of 3: 1min 27s per loop

%timeit gmpy.comb(x, y)
1 loops, best of 3: 1.97 s per loop

%timeit int(sympy.binomial(x, y))
100000 loops, best of 3: 5.06 µs per loop

22

多くの場合、数学的な定義の文字通りの変換は非常に適切です(Pythonは自動的に大きな数の計算を使用することを思い出してください)。

from math import factorial

def calculate_combinations(n, r):
    return factorial(n) // factorial(r) // factorial(n-r)

私がテストしたいくつかの入力(たとえば、n = 1000 r = 500)の場合、これはreduce、別の(現在最も投票されている)回答で提案されている1つのライナーよりも10倍以上高速でした。一方、@ JF Sebastianが提供する抜粋により、パフォーマンスは向上しています。


11

以降Python 3.8、標準ライブラリにmath.combは二項係数を計算する関数が含まれています。

math.comb(n、k)

これは、繰り返しなしでnアイテムからkアイテムを選択する方法の数です
n! / (k! (n - k)!)

import math
math.comb(10, 5) # 252

10

ここに別の選択肢があります。これはもともとC ++で書かれていたため、有限精度の整数(__int64など)をC ++にバックポートできます。利点は、(1)整数演算のみを含み、(2)乗算と除算の連続するペアを実行することで整数値の肥大化を回避することです。Nas BanovのPascal三角形で結果をテストしたところ、正しい答えが得られました。

def choose(n,r):
  """Computes n! / (r! (n-r)!) exactly. Returns a python long int."""
  assert n >= 0
  assert 0 <= r <= n

  c = 1L
  denom = 1
  for (num,denom) in zip(xrange(n,n-r,-1), xrange(1,r+1,1)):
    c = (c * num) // denom
  return c

理論的根拠:乗算と除算の数を最小限に抑えるために、式を次のように書き換えます。

    n!      n(n-1)...(n-r+1)
--------- = ----------------
 r!(n-r)!          r!

乗算のオーバーフローをできるだけ回避するために、次のSTRICT順序で左から右に評価します。

n / 1 * (n-1) / 2 * (n-2) / 3 * ... * (n-r+1) / r

この順序で演算された整数演算が正確であることを示すことができます(つまり、丸め誤差はありません)。


5

動的プログラミングを使用すると、時間の複雑さはΘ(n * m)であり、空間の複雑さはΘ(m)です。

def binomial(n, k):
""" (int, int) -> int

         | c(n-1, k-1) + c(n-1, k), if 0 < k < n
c(n,k) = | 1                      , if n = k
         | 1                      , if k = 0

Precondition: n > k

>>> binomial(9, 2)
36
"""

c = [0] * (n + 1)
c[0] = 1
for i in range(1, n + 1):
    c[i] = 1
    j = i - 1
    while j > 0:
        c[j] += c[j - 1]
        j -= 1

return c[k]

4

プログラムにn(たとえばn <= N)の上限があり、nCrを繰り返し計算する必要がある場合(できれば>> N回)、lru_cacheを使用すると、パフォーマンスが大幅に向上します。

from functools import lru_cache

@lru_cache(maxsize=None)
def nCr(n, r):
    return 1 if r == 0 or r == n else nCr(n - 1, r - 1) + nCr(n - 1, r)

キャッシュの構築(暗黙的に行われます)には時間がかかりますO(N^2)。以降の呼び出しnCrはに戻りO(1)ます。


4

実際にscipy.special.combを使用するよりも約5〜8倍速いことが判明した2つの単純な関数を記述できます。実際、追加のパッケージをインポートする必要はなく、関数は非常に簡単に読み取ることができます。秘訣は、メモ化を使用して以前に計算された値を保存し、nCrの定義を使用することです

# create a memoization dictionary
memo = {}
def factorial(n):
    """
    Calculate the factorial of an input using memoization
    :param n: int
    :rtype value: int
    """
    if n in [1,0]:
        return 1
    if n in memo:
        return memo[n]
    value = n*factorial(n-1)
    memo[n] = value
    return value

def ncr(n, k):
    """
    Choose k elements from a set of n elements - n must be larger than or equal to k
    :param n: int
    :param k: int
    :rtype: int
    """
    return factorial(n)/(factorial(k)*factorial(n-k))

時間を比較すると

from scipy.special import comb
%timeit comb(100,48)
>>> 100000 loops, best of 3: 6.78 µs per loop

%timeit ncr(100,48)
>>> 1000000 loops, best of 3: 1.39 µs per loop

最近、コードを簡略化する可能性があるlru_cacheと呼ばれるfunctoolsにメモ化デコレータがありますか?
痴呆ハリネズミ

2

sympyを使用すると、非常に簡単です。

import sympy

comb = sympy.binomial(n, r)

2

Pythonで配布されている標準ライブラリのみを使用する:

import itertools

def nCk(n, k):
    return len(list(itertools.combinations(range(n), k)))

3
時間の複雑さ(およびメモリ使用量)は許容できるとは思いません。
xmcp 2017

2

直接式は、nが20より大きい場合、大きな整数を生成します。

したがって、さらに別の応答:

from math import factorial

reduce(long.__mul__, range(n-r+1, n+1), 1L) // factorial(r)

これはlongを使用することでpythonの大きな整数を回避するため、短く、正確で、効率的です。

scipy.special.combと比較すると、より正確で高速です。

 >>> from scipy.special import comb
 >>> nCr = lambda n,r: reduce(long.__mul__, range(n-r+1, n+1), 1L) // factorial(r)
 >>> comb(128,20)
 1.1965669823265365e+23
 >>> nCr(128,20)
 119656698232656998274400L  # accurate, no loss
 >>> from timeit import timeit
 >>> timeit(lambda: comb(n,r))
 8.231969118118286
 >>> timeit(lambda: nCr(128, 20))
 3.885951042175293

これは間違っています!N == rは、結果は、このコード戻り0 1であるかどうか
reyammer

より正確には、のrange(n-r+1, n+1)代わりにすべきですrange(n-r,n+1)
reyammer 2016年

1

これは、組み込みのメモ化デコレータを使用した@ killerT2333コードです。

from functools import lru_cache

@lru_cache()
def factorial(n):
    """
    Calculate the factorial of an input using memoization
    :param n: int
    :rtype value: int
    """
    return 1 if n in (1, 0) else n * factorial(n-1)

@lru_cache()
def ncr(n, k):
    """
    Choose k elements from a set of n elements,
    n must be greater than or equal to k.
    :param n: int
    :param k: int
    :rtype: int
    """
    return factorial(n) / (factorial(k) * factorial(n - k))

print(ncr(6, 3))

1

ここにあなたのための効率的なアルゴリズムがあります

for i = 1.....r

   p = p * ( n - i ) / i

print(p)

たとえば、nCr(30,7)= fact(30)/(fact(7)* fact(23))=(30 * 29 * 28 * 27 * 26 * 25 * 24)/(1 * 2 * 3 * 4 * 5 * 6 * 7)

したがって、1からrまでのループを実行するだけで結果を得ることができます。


0

それはおそらく、かなり大きな入力に対して純粋なpythonで実行できるのと同じくらい高速です。

def choose(n, k):
    if k == n: return 1
    if k > n: return 0
    d, q = max(k, n-k), min(k, n-k)
    num =  1
    for n in xrange(d+1, n+1): num *= n
    denom = 1
    for d in xrange(1, q+1): denom *= d
    return num / denom

0

この機能は非常に最適化されています。

def nCk(n,k):
    m=0
    if k==0:
        m=1
    if k==1:
        m=n
    if k>=2:
        num,dem,op1,op2=1,1,k,n
        while(op1>=1):
            num*=op2
            dem*=op1
            op1-=1
            op2-=1
        m=num//dem
    return m
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.