2016-05-30 16 views
43

Ich verglich die Leistung der mean Funktion des statistics Moduls mit der einfachen sum(l)/len(l) Methode und fand die mean Funktion aus irgendeinem Grund sehr langsam. Ich benutze timeit mit den zwei Code-Schnipsel unten, um sie zu vergleichen, weiß jemand, was den massiven Unterschied in der Ausführungsgeschwindigkeit verursacht? Ich benutze Python 3.5.Warum ist statistics.mean() so langsam?

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))) 

Der obige Code wird in etwa 0,043 Sekunden auf meinem Computer ausgeführt.

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))) 

Der obige Code wird in etwa 0,000565 Sekunden auf meinem Computer ausgeführt.

+4

die tl; dr ist, dass 'mean' ist viel mehr Fehlerbehandlung und Arbeit als eine triviale' Summe (x)/len (x) 'tun. –

+9

Nicht nur langsam, sondern auch gemein. :( –

Antwort

65

statistics Moduls Python nicht auf Geschwindigkeit ausgelegt ist, aber für Präzision

In the specs for this module scheint es, dass

Die integrierte Summe kann die Genauigkeit verlieren, wenn sie mit schwebenden unterschiedlichen Größen umgehen. Folglich schlägt der oben naive Mittelwert diesen "Folter-Test"

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

Rückkehr 0 statt 1, ein rein rechnerischen Fehler von 100%.

Verwendung von math.fsum innerhalb Mittelwert wird es genauer mit float Daten, aber es hat auch den Nebeneffekt der Umwandlung von Argumenten in float, auch wenn unnötig. Z.B. Wir sollten erwarten, dass der Mittelwert einer Liste von Brüchen eine Bruchzahl ist, kein Float.

Umgekehrt, wenn wir einen Blick auf die Umsetzung der _sum() in diesem Modul die ersten Zeilen der docstring Methode seem to confirm that:

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. 

    [...] """ 

Also ja, statistics Implementierung von sum, statt eine des Seins einfacher One-Liner-Aufruf an Python eingebaute sum() Funktion, dauert etwa 20 Zeilen von selbst mit einer verschachtelten for Schleife in seinem Körper.

Das passiert, weil statistics._sum wählt, um die maximale Genauigkeit für alle Arten von Zahl zu garantieren, die es treffen könnte (selbst wenn sie sich weit voneinander unterscheiden), anstatt einfach Geschwindigkeit zu betonen.

Daher erscheint es normal, dass die eingebaute in sum hundertmal schneller beweist. Die Kosten dafür, dass es eine viel niedrigere Präzision in Ihnen ist, nennen Sie es mit exotischen Zahlen.

Andere Optionen

Wenn Sie Geschwindigkeit in Ihre Algorithmen priorisieren müssen, sollten Sie einen Blick auf Numpy stattdessen haben, die Algorithmen, von denen in C.

NumPy bedeuten umgesetzt ist nicht so präzise als statistics durch eine Totale, aber es implementiert (seit 2013) eine routine based on pairwise summation, die besser ist als eine naive sum/len (mehr Infos in der Verbindung).

jedoch ...

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 
+0

Ich habe einen ähnlichen Test für die stdev-Funktion des Moduls durchgeführt und festgestellt, dass sie etwa 120 Mal langsamer ist als meine eigene einfache Implementierung. Diese Funktion muss wirklich eine Menge zusätzlicher Arbeit leisten, nur um die zusätzliche Präzision zu erreichen, die ich momentan sowieso nicht brauche. Danke, dass Sie mich aufgeklärt haben. –

+0

Ich bin nicht so vertraut mit Pandas, aber NumPy ist für Geschwindigkeit gebaut. Ich glaube nicht, dass es eine durchschnittliche Routine für die numerische Stabilität wie 'statistics.mean' hat; es macht nur die naive Summe/len, aber in C. – user2357112

+4

Ausreichend aktuelle NumPy Versionen haben [eine verbesserte Summierungsroutine basierend auf paarweiser Summierung] (https://github.com/numpy/numpy/pull/3685), aber nein Option für Kahan summation, geschweige denn alles, was zu den Längen führt, die das 'statistics' Modul für die Genauigkeit benötigt. – user2357112

2

Nach diesem Beitrag: Calculating arithmetic mean (average) in Python

Es sollte „in der Statistik besonders präzise Umsetzung der Summe Betreiber durch“.

Die mittlere Funktion ist mit einer inneren _sum-Funktion codiert, die genauer als die normale Addition sein soll, aber viel langsamer ist (hier verfügbarer Code: https://hg.python.org/cpython/file/3.5/Lib/statistics.py).

Es ist in dem PEP angegeben: https://www.python.org/dev/peps/pep-0450/ Genauigkeit als Geschwindigkeit für dieses Modul als wichtiger angesehen wird.

+0

[Link zu '_sum' Funktion in statistics.py] (https://github.com/python/cpython/blob/master/Lib/statistics.py#L120) Es bevorzugt definitiv Fraktionen über alles andere ... –

5

fragte ich die gleiche Frage eine Weile zurück, aber sobald ich die _sum Funktion der mittleren auf Linie 317 in der Quelle genannt bemerkt habe ich verstanden, warum:

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) 

Es gibt eine Vielzahl von Operationen im Vergleich geschieht nur zu Aufruf der eingebauten sum, nach den Doc-Strings mean berechnet eine hochpräzise Summe.

Sie können sehen, Mittelwert mit vs Summe Sie verschiedene Ausgabe geben kann:

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 
6

wenn Sie tun Pflege Geschwindigkeit Verwendung numpy/scipy/Pandas statt:

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 

Fazit: es wird schnelle Größenordnung - in meinem Zum Beispiel war es 700 mal schneller, aber vielleicht nicht so genau (da numpy keinen Kahan-Summierungsalgorithmus verwendet).

+3

"Sie werden das gleiche präzise Ergebnis haben" - nein, Sie werden die Präzision verlieren. Wie viel Sie verlieren und ob Sie sich darum kümmern, hängt davon ab, wie die Eingabe aussieht und wofür Sie die Ergebnisse verwenden. – user2357112

+0

@ user2357112, danke für deinen Kommentar. Ich habe meine Antwort aktualisiert – MaxU

5

Beiden len() und sum() sind Python eingebaute Funktionen (mit eingeschränkter Funktionalität), die wichtiger ist in C und geschrieben werden, optimiert werden schnell arbeiten mit bestimmten Arten oder Objekten (Liste).

Sie können hier bei der Umsetzung der eingebauten Funktionen aussehen:

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

Die statistics.mean() ist eine hohe Funktion in Python geschrieben. Schauen Sie hier, wie es umgesetzt wird:

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

Sie können sehen, dass später intern verwendet eine andere Funktion aufgerufen _sum(), die ein paar zusätzliche Kontrollen hat im Vergleich zu den eingebauten Funktionen.