Техническое задание на проектирование для nan_policy#
Многие функции в scipy.stats имеют параметр с именем nan_policy
который определяет, как функция обрабатывает данные, содержащие nan. В
этом разделе мы предоставляем руководства для разработчиков SciPy о том, как nan_policy
предназначен для использования, чтобы обеспечить согласованный API при добавлении этого параметра к новым функциям.
Базовый API#
Параметр nan_policy принимает три возможные строки: 'omit',
'raise' и 'propagate'. Значения:
nan_policy='omit': Игнорировать вхожденияnanво входных данных. Не генерировать предупреждение, если входные данные содержатnan(если эквивалентный ввод сnanудаленные значения вызовут предупреждение). Например, для простого случая функции, которая принимает один массив и возвращает скаляр (и игнорируя возможное использованиеaxisна данный момент):func([1.0, 3.0, np.nan, 5.0], nan_policy='omit')
должен вести себя так же, как:
func([1.0, 3.0, 5.0])
Более общо, для функций, возвращающих скаляр,
func(a, nan_policy='omit')должен вести себя так же, какfunc(a[~np.isnan(a)]).Для функций, которые преобразуют вектор в новый вектор того же размера и для которых каждый элемент выходного массива зависит от более чем одного соответствующего значения во входном массиве [1] (например,
scipy.stats.zscore,scipy.stats.boxcoxкогдаlmbdaравно None),:y = func(a, nan_policy='omit')
должен вести себя так же, как:
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
(В общем случае, dtype
yможет зависеть отaи на ожидаемое поведениеfunc). Другими словами, a nan во входных данных даёт соответствующий nan в выводе, но наличие этого nan не влияет на вычисление не-nan значения.Модульные тесты для этого свойства должны использоваться для тестирования функций, которые обрабатывают
nan_policy.Для функций, которые возвращают скаляр и принимают два или более аргументов, но чьи значения не связаны (например,
scipy.stats.ansari,scipy.stats.f_oneway), та же идея применяется к каждому входному массиву. Итак:func(a, b, nan_policy='omit')
должен вести себя так же, как:
func(a[~np.isnan(a)], b[~np.isnan(b)])
Для входных данных с связанный или paired значения (например,
scipy.stats.pearsonr,scipy.stats.ttest_rel) рекомендуется опускать все значения, для которых любые из связанных значений являютсяnan. Для функции с двумя связанными входными массивами это означает:y = func(a, b, nan_policy='omit')
должен вести себя так же, как:
hasnan = np.isnan(a) | np.isnan(b) # Union of the isnan masks. y = func(a[~hasnan], b[~hasnan])
Docstring для такой функции должен чётко описывать это поведение.
nan_policy='raise': ВызватьValueError.nan_policy='propagate': Распространениеnanзначение к выходу. Обычно это означает просто выполнить функцию без проверки наnan, но см.для примера, где это может привести к неожиданному выводу.
nan_policy в сочетании с axis параметр#
Здесь нет ничего удивительного - упомянутый выше принцип все еще применяется, когда функция имеет axis параметр. Предположим, например,
func сводит одномерный массив к скаляру и обрабатывает n-мерные массивы как коллекцию одномерных массивов, с axis параметр, указывающий ось, вд которой применяется редукция. Если, скажем:
func([1, 3, 4]) -> 10.0
func([2, -3, 8, 2]) -> 4.2
func([7, 8]) -> 9.5
func([]) -> -inf
тогда:
func([[ 1, nan, 3, 4],
[ 2, -3, 8, 2],
[nan, 7, nan, 8],
[nan, nan, nan, nan]], nan_policy='omit', axis=-1)
должен дать результат:
np.array([10.0, 4.2, 9.5, -inf])
Крайние случаи#
Функция, реализующая nan_policy параметр должен корректно обрабатывать случай, когда все значения во входном массиве(ах) являются nan.
Основной принцип, описанный выше, все еще применяется:
func([nan, nan, nan], nan_policy='omit')
должен вести себя так же, как:
func([])
На практике, при добавлении nan_policy к существующей функции, часто оказывается, что функция ещё не обрабатывает этот случай определённым образом, и может потребоваться продумать и спроектировать, чтобы она работала. Правильное поведение (будь то возврат nan, возвращать другое значение, вызывать исключение или что-то ещё) будет определяться в каждом конкретном случае.
Почему не nan_policy также применяются к inf?#
Хотя в школе нас учат, что «бесконечность — это не число», значения с плавающей точкой nan и inf качественно различны. Значения inf и -inf ведут себя гораздо больше как обычные значения с плавающей точкой, чем nan.
Можно сравнить
infк другим значениям с плавающей точкой, и он ведёт себя ожидаемо, например,3 < infравно True.В основном, арифметика работает "как ожидается" с
inf, например.inf + inf = inf,-2*inf = -inf,1/inf = 0, и т.д.Многие существующие функции работают "как ожидается" с
inf:np.log(inf) = inf,np.exp(-inf) = 0,np.array([1.0, -1.0, np.inf]).min() = -1.0, и т.д.
Таким образом, пока nan почти всегда означает «что-то пошло не так» или «чего-то не хватает», inf во многих случаях может рассматриваться как полезное значение с плавающей точкой.
Это также согласуется с NumPy nan функции, которые не игнорировать
inf:
>>> 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
Как не реализовать nan_policy#
В прошлом (и возможно сейчас), некоторые stats обрабатываемые функции
nan_policy с использованием маскированного массива для маскировки nan значения, и
затем вычисление результата с использованием функций в mstats подпакет.
Проблема этого подхода в том, что код маскированного массива может преобразовать
inf в замаскированное значение, чего мы не хотим делать (см. выше). Это также означает, что если не проявить осторожность, возвращаемым значением будет замаскированный массив, что, вероятно, удивит пользователя, если он передал обычные массивы.
Сноски