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 vonnanin der Eingabe ignorieren. Keine Warnung ausgeben, wenn die Eingabenanenthält (es sei denn, die äquivalente Eingabe ohne dienan-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 vonaxisvorerst 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 wiefunc(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
yvonaund vom erwarteten Verhalten vonfuncabhä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_policybehandeln.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. Alsofunc(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 Wertenanist. Für eine Funktion mit zwei zusammenhängenden Array-Eingaben bedeutet diesy = 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 einenValueErroraus.nan_policy='propagate': Überträgt dennan-Wert in die Ausgabe. Typischerweise bedeutet dies einfach, die Funktion auszuführen, ohne aufnanzu prüfen, aber siehefü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
infmit anderen Gleitkommazahlen vergleichen, und es verhält sich wie erwartet, z. B. ist3 < infTrue.Größtenteils funktioniert die Arithmetik mit
inf„wie erwartet“, z. B.inf + inf = inf,-2*inf = -inf,1/inf = 0usw.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.0usw.
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