Eine Designspezifikation für nan_policy#

Viele Funktionen in scipy.stats haben einen Parameter namens nan_policy, der bestimmt, wie die Funktion mit Daten umgeht, die nan enthalten. In diesem Abschnitt geben wir Richtlinien für SciPy-Entwickler an, wie nan_policy verwendet werden soll, um sicherzustellen, dass bei der Hinzufügung dieses Parameters zu neuen Funktionen eine konsistente API beibehalten wird.

Die grundlegende API#

Der Parameter nan_policy akzeptiert drei mögliche Zeichenfolgen: 'omit', 'raise' und 'propagate'. Die Bedeutungen sind

  • nan_policy='omit': Vorkommen von nan in der Eingabe ignorieren. Keine Warnung ausgeben, wenn die Eingabe nan enthält (es sei denn, die äquivalente Eingabe ohne die nan-Werte würde eine Warnung auslösen). Zum Beispiel, für den einfachen Fall einer Funktion, die ein einzelnes Array akzeptiert und einen Skalar zurückgibt (und die mögliche Verwendung von axis vorerst ignoriert)

    func([1.0, 3.0, np.nan, 5.0], nan_policy='omit')
    

    sollte sich genauso verhalten wie

    func([1.0, 3.0, 5.0])
    

    Allgemeiner gesagt, für Funktionen, die einen Skalar zurückgeben, sollte func(a, nan_policy='omit') dasselbe Verhalten wie func(a[~np.isnan(a)]) zeigen.

    Für Funktionen, die einen Vektor in einen neuen Vektor gleicher Größe transformieren und bei denen jeder Eintrag im Ausgabearray von mehr als nur dem entsprechenden Wert im Eingabearray abhängt [1] (z. B. scipy.stats.zscore, scipy.stats.boxcox *wenn* lmbda *None* ist),

    y = func(a, nan_policy='omit')
    

    sollte sich genauso verhalten wie

    nan_mask = np.isnan(a)
    y = np.empty(a.shape, dtype=np.float64)
    y[~nan_mask] = func(a[~nan_mask])
    y[nan_mask] = np.nan
    

    (Im Allgemeinen kann der dtype von y von a und vom erwarteten Verhalten von func abhängen). Mit anderen Worten, ein nan in der Eingabe führt zu einem entsprechenden nan in der Ausgabe, aber die Anwesenheit dieses nan beeinflusst nicht die Berechnung der Nicht-nan-Werte.

    Einheitentests für diese Eigenschaft sollten verwendet werden, um Funktionen zu testen, die nan_policy behandeln.

    Für Funktionen, die einen Skalar zurückgeben und zwei oder mehr Argumente akzeptieren, deren Werte aber nicht zusammenhängen (z. B. scipy.stats.ansari, scipy.stats.f_oneway), gilt die gleiche Idee für jedes Eingabearray. Also

    func(a, b, nan_policy='omit')
    

    sollte sich genauso verhalten wie

    func(a[~np.isnan(a)], b[~np.isnan(b)])
    

    Für Eingaben mit *zusammenhängenden* oder *gepaarten* Werten (z. B. scipy.stats.pearsonr, scipy.stats.ttest_rel) ist das empfohlene Verhalten, alle Werte zu ignorieren, für die einer der zusammenhängenden Werte nan ist. Für eine Funktion mit zwei zusammenhängenden Array-Eingaben bedeutet dies

    y = func(a, b, nan_policy='omit')
    

    sollte sich genauso verhalten wie

    hasnan = np.isnan(a) | np.isnan(b)  # Union of the isnan masks.
    y = func(a[~hasnan], b[~hasnan])
    

    Die Dokumentation einer solchen Funktion sollte dieses Verhalten klar darlegen.

  • nan_policy='raise': Löst einen ValueError aus.

  • nan_policy='propagate': Überträgt den nan-Wert in die Ausgabe. Typischerweise bedeutet dies einfach, die Funktion auszuführen, ohne auf nan zu prüfen, aber siehe

    für ein Beispiel, wo das zu unerwarteten Ausgaben führen kann.

nan_policy kombiniert mit einem axis-Parameter#

Hier gibt es nichts Überraschendes – das oben genannte Prinzip gilt weiterhin, wenn die Funktion einen axis-Parameter hat. Angenommen, z. B. reduziert func ein 1D-Array auf einen Skalar und behandelt nD-Arrays als Sammlung von 1D-Arrays, wobei der axis-Parameter die Achse angibt, entlang der die Reduktion angewendet werden soll. Wenn z. B.

func([1, 3, 4])     -> 10.0
func([2, -3, 8, 2]) ->  4.2
func([7, 8])        ->  9.5
func([])            -> -inf

dann muss

func([[  1, nan,   3,   4],
      [  2,  -3,   8,   2],
      [nan,   7, nan,   8],
      [nan, nan, nan, nan]], nan_policy='omit', axis=-1)

das Ergebnis geben

np.array([10.0, 4.2, 9.5, -inf])

Randfälle#

Eine Funktion, die den nan_policy-Parameter implementiert, sollte den Fall, dass *alle* Werte im Eingabearray (den Eingabearrays) nan sind, ordnungsgemäß behandeln. Das grundlegende Prinzip gilt weiterhin

func([nan, nan, nan], nan_policy='omit')

sollte sich genauso verhalten wie

func([])

In der Praxis ist es bei der Hinzufügung von nan_policy zu einer bestehenden Funktion nicht ungewöhnlich festzustellen, dass die Funktion diesen Fall noch nicht auf definierte Weise behandelt und einige Überlegungen und Designarbeit erforderlich sind, um sicherzustellen, dass sie funktioniert. Das korrekte Verhalten (ob es nun die Rückgabe von nan, die Rückgabe eines anderen Wertes, das Auslösen einer Ausnahme oder etwas anderes ist) wird von Fall zu Fall entschieden.

Warum gilt nan_policy nicht auch für inf?#

Obwohl wir in der Grundschule lernen, dass „Unendlichkeit keine Zahl ist“, sind die Gleitkommazahlen nan und inf qualitativ unterschiedlich. Die Werte inf und -inf verhalten sich viel mehr wie normale Gleitkommazahlen als nan.

  • Man kann inf mit anderen Gleitkommazahlen vergleichen, und es verhält sich wie erwartet, z. B. ist 3 < inf True.

  • Größtenteils funktioniert die Arithmetik mit inf „wie erwartet“, z. B. inf + inf = inf, -2*inf = -inf, 1/inf = 0 usw.

  • Viele bestehende Funktionen funktionieren „wie erwartet“ mit inf: np.log(inf) = inf, np.exp(-inf) = 0, np.array([1.0, -1.0, np.inf]).min() = -1.0 usw.

Während nan fast immer bedeutet „etwas ist schiefgegangen“ oder „etwas fehlt“, kann inf in vielen Fällen als nützlicher Gleitkommawert behandelt werden.

Es ist auch konsistent mit den NumPy nan-Funktionen, inf nicht zu ignorieren

>>> np.nanmax([1, 2, 3, np.inf, np.nan])
inf
>>> np.nansum([1, 2, 3, np.inf, np.nan])
inf
>>> np.nanmean([8, -np.inf, 9, 1, np.nan])
-inf

Wie nan_policy *nicht* implementiert werden sollte#

In der Vergangenheit (und möglicherweise auch derzeit) behandelten einige stats-Funktionen nan_policy, indem sie ein maskiertes Array verwendeten, um die nan-Werte zu maskieren, und dann das Ergebnis mit den Funktionen aus dem mstats-Unterpaket berechneten. Das Problem bei diesem Ansatz ist, dass der maskierte Array-Code inf in einen maskierten Wert umwandeln könnte, was wir nicht wollen (siehe oben). Es bedeutet auch, dass, wenn nicht sorgfältig vorgegangen wird, der Rückgabewert ein maskiertes Array sein wird, was für den Benutzer wahrscheinlich eine Überraschung darstellt, wenn er reguläre Arrays übergeben hat.

Fußnoten