web-dev-qa-db-ja.com

なぜstatistics.mean()はとても遅いのですか?

meanモジュールのstatistics関数のパフォーマンスを単純なsum(l)/len(l)メソッドと比較したところ、mean関数が何らかの理由で非常に遅いことがわかりました。 timeitを以下の2つのコードスニペットと比較して比較しましたが、実行速度に大きな違いが生じる原因を誰かが知っていますか? Python 3.5を使用しています。

from timeit import repeat
print(min(repeat('mean(l)',
                 '''from random import randint; from statistics import mean; \
                 l=[randint(0, 10000) for i in range(10000)]''', repeat=20, number=10)))

上記のコードは私のマシンで約0.043秒で実行されます。

from timeit import repeat
print(min(repeat('sum(l)/len(l)',
                 '''from random import randint; from statistics import mean; \
                 l=[randint(0, 10000) for i in range(10000)]''', repeat=20, number=10)))

上記のコードは私のマシンで約0.000565秒で実行されます。

45
Just some guy

Pythonのstatisticsモジュールは速度を上げるために構築されたのではなく、精度を高めるために構築されています

このモジュールの仕様 では、

大きさが大きく異なる浮動小数点数を処理する場合、組み込みの合計は精度を失う可能性があります。その結果、上記の素朴な平均はこの「拷問テスト」に失敗します

assert mean([1e30, 1, 3, -1e30]) == 1

1ではなく0を返します。100%の純粋な計算エラーです。

平均内でmath.fsumを使用すると、浮動小数点データでより正確になりますが、不要な場合でも引数を浮動小数点に変換するという副作用もあります。例えば。 Fractionsのリストの平均は、floatではなくFractionであると期待する必要があります。

逆に、このモジュールの_sum()の実装を見ると、メソッドのdocstringの最初の行 確認するように見えます

_def _sum(data, start=0):
    """_sum(data [, start]) -> (type, sum, count)

    Return a high-precision sum of the given numeric data as a fraction,
    together with the type to be converted to and the count of items.

    [...] """
_

ええ、そうです、statisticssumの実装は、Pythonの組み込みsum()関数への単純な1行の呼び出しではなく、入れ子になったそれ自体で約20行かかります本体でforループ。

これは、速度を重視するのではなく、_statistics._sum_が遭遇する可能性のあるすべてのタイプの数値の最大精度を保証することを選択するために発生します。

したがって、組み込みのsumが100倍速くなるのは正常なようです。精度がはるかに低いため、エキゾチックな数値で呼び出すことになります。

その他のオプション

アルゴリズムで速度を優先する必要がある場合は、代わりに Numpy を確認してください。アルゴリズムはCで実装されています。

NumPyの平均値は、ロングショットではstatisticsほど正確ではありませんが、実装されています(2013年以降) ペアワイズ合計に基づくルーチン これは、素朴な_sum/len_よりも優れています(詳細リンクの情報)。

しかしながら...

_import numpy as np
import statistics

np_mean = np.mean([1e30, 1, 3, -1e30])
statistics_mean = statistics.mean([1e30, 1, 3, -1e30])

print('NumPy mean: {}'.format(np_mean))
print('Statistics mean: {}'.format(statistics_mean))

> NumPy mean: 0.0
> Statistics mean: 1.0
_
69
Jivan

速度を気にする場合は、代わりにnumpy/scipy/pandasを使用してください。

In [119]: from random import randint; from statistics import mean; import numpy as np;

In [122]: l=[randint(0, 10000) for i in range(10**6)]

In [123]: mean(l)
Out[123]: 5001.992355

In [124]: %timeit mean(l)
1 loop, best of 3: 2.01 s per loop

In [125]: a = np.array(l)

In [126]: np.mean(a)
Out[126]: 5001.9923550000003

In [127]: %timeit np.mean(a)
100 loops, best of 3: 2.87 ms per loop

結論:桁違いに速くなります-私の例では700倍速くなりましたが、おそらくそれほど正確ではありません(numpyはKahan加算アルゴリズムを使用していないため)。

6
MaxU

しばらく前に同じ質問をしましたが、気づいたら_sum関数が行で平均で呼び出されました 17 ソースで私は理由を理解しました:

def _sum(data, start=0):
    """_sum(data [, start]) -> (type, sum, count)
    Return a high-precision sum of the given numeric data as a fraction,
    together with the type to be converted to and the count of items.
    If optional argument ``start`` is given, it is added to the total.
    If ``data`` is empty, ``start`` (defaulting to 0) is returned.
    Examples
    --------
    >>> _sum([3, 2.25, 4.5, -0.5, 1.0], 0.75)
    (<class 'float'>, Fraction(11, 1), 5)
    Some sources of round-off error will be avoided:
    >>> _sum([1e50, 1, -1e50] * 1000)  # Built-in sum returns zero.
    (<class 'float'>, Fraction(1000, 1), 3000)
    Fractions and Decimals are also supported:
    >>> from fractions import Fraction as F
    >>> _sum([F(2, 3), F(7, 5), F(1, 4), F(5, 6)])
    (<class 'fractions.Fraction'>, Fraction(63, 20), 4)
    >>> from decimal import Decimal as D
    >>> data = [D("0.1375"), D("0.2108"), D("0.3061"), D("0.0419")]
    >>> _sum(data)
    (<class 'decimal.Decimal'>, Fraction(6963, 10000), 4)
    Mixed types are currently treated as an error, except that int is
    allowed.
    """
    count = 0
    n, d = _exact_ratio(start)
    partials = {d: n}
    partials_get = partials.get
    T = _coerce(int, type(start))
    for typ, values in groupby(data, type):
        T = _coerce(T, typ)  # or raise TypeError
        for n,d in map(_exact_ratio, values):
            count += 1
            partials[d] = partials_get(d, 0) + n
    if None in partials:
        # The sum will be a NAN or INF. We can ignore all the finite
        # partials, and just look at this special one.
        total = partials[None]
        assert not _isfinite(total)
    else:
        # Sum all the partial sums using builtin sum.
        # FIXME is this faster if we sum them in order of the denominator?
        total = sum(Fraction(n, d) for d, n in sorted(partials.items()))
    return (T, total, count)

ドキュメント文字列sum高精度合計を計算するように、組み込みのmeanを呼び出すだけと比較して、多数の操作が発生しています。

平均と合計を使用すると、さまざまな出力が得られることがわかります。

In [7]: l = [.1, .12312, 2.112, .12131]

In [8]: sum(l) / len(l)
Out[8]: 0.6141074999999999

In [9]: mean(l)
Out[9]: 0.6141075
5

Len()とsum()は両方ともPython組み込み関数(機能制限あり))であり、Cで記述され、さらに重要なことに、特定のタイプまたはオブジェクト(リスト)で高速に動作するように最適化されています。

ここで組み込み関数の実装を見ることができます:

https://hg.python.org/sandbox/python2.7/file/tip/Python/bltinmodule.c

Statistics.mean()は、Pythonで記述された高レベルの関数です。それがどのように実装されているかをここで見てみましょう:

https://hg.python.org/sandbox/python2.7/file/tip/Lib/statistics.py

後で内部的に_sum()と呼ばれる別の関数を使用していることがわかります。この関数は、組み込み関数と比較していくつかの追加チェックを実行します。

5
grepe

その投稿によると: Pythonでの算術平均(平均)の計算

これは、「統計でのsum演算子の特に正確な実装のため」です。

平均関数は、通常の加算よりも正確であるはずの内部_sum関数でコード化されていますが、はるかに低速です(コードはこちらから入手できます: https://hg.python.org/cpython/file/ 3.5/Lib/statistics.py )。

それはPEPで指定されています: https://www.python.org/dev/peps/pep-0450/ そのモジュールの速度として、精度はより重要であると考えられています。

2