Булевы операции и функции, значения inf и nan

Продолжаем знакомство с операциями над массивами и посмотрим как они ведут себя с булевыми операциями. Предположим, имеется одномерный массив:

a = np.array([1, 2, 3, 10, 20, 30])

и мы хотим определить все числа, которые больше 5. Мы с вами уже выполняли такую операцию и для этого сначала формировали булевый массив, а затем, выделяли элементы, у индексов которых стоит значение True:

indx = a > 5
a[indx]

На выходе получим массив из трех элементов, которым соответствуют позиции True:

array([10, 20, 30])

Видите, как это может быть удобно: выделить нужные элементы, не используя ни одного оператора цикла языка Python. А, значит, такая конструкция будет работать достаточно быстро (так как внутри реализована на языках Си и Fortran).

Конечно, эту запись можно еще упростить и записать в виде:

a[ a > 5 ]

Результат будет тем же. По аналогии работают и другие булевы операторы:

Оператор

Описание

a == b

Проверка на равенство

a != b

Проверка на неравенство

a > b

Проверка, что a больше b

a < b

Проверка, что a меньше b

a >= b

Проверка, что a больше или равно b

a <= b

Проверка, что a меньше или равно b

Здесь в качестве операндов a и b могут выступать как числа, так и массивы NumPy. Например, добавим еще один массив:

b = np.array([1, 2, 3, 4, 5, 6])

Тогда можно использовать сравнения:

a == b    # array([ True,  True,  True, False, False, False])
a >= b    # array([ True,  True,  True,  True,  True,  True])
a <= b    # array([ True,  True,  True, False, False, False])
a != b    # array([False, False, False,  True,  True,  True])

Функции greater, less, equal

Вместо записи операторов в NumPy имеются функции сравнения: greater(), less() и equal(). Их названия говорят сами за себя:

  • greater(a, b) – выполняет сравнение a > b;
  • less(a, b) – выполняет сравнение a < b;
  • equal(a, b) – выполняет сравнение a == b.

Использование их вполне очевидно, например:

np.greater(a, b) # array([False, False,  True, False])
np.less(a, b) # array([ True,  True, False, False])
np.equal(a, b) # array([False, False, False,  True])

Но, чаще всего на практике вместо них записывают булевы операторы: >, <, ==.

Функции array_equal, all и any

Но использовать результат сравнения в условных операторах нельзя. Следующая строчка приведет к ошибке:

if(a == b): print("a == b")

Для такого сравнения массивов необходимо получать только одно значение True или False, а не объект array. Для этого в пакете NumPy существуют специальная функция np.array_equal(), которую можно применить так:

if np.array_equal(a ,b): 
     print("a == b")

Это условие сработает, если оба массива a и b содержат одинаковые значения элементов и равны по длине.

Если нам нужно определить, что хотя бы один элемент массива удовлетворяет указанному условию, то можно воспользоваться функцией any(), например:

# для массива a = array([ 1,  2,  3, 10, 20, 30])
np.any(a > 5)    # True 
np.any(a == 5)    # False
np.any(a == b)    # True

Если же нужно узнать, все ли элемента массива удовлетворяют условию, то используется функция all():

np.all(a > 5)       # False
np.all(a > 0)       # True
np.all(a == b)     # False

Значения -inf, inf и nan

Пакет NumPy реализован максимально дружественным способом и там, где можно избежать ошибок и продолжить вычисления, он это делает. Например, давайте разделим все значения массива a на 0. Из математики мы знаем, что на 0 делить нельзя, но, тем не менее, критической ошибки не возникнет, а все элементы примут значение inf:

a/0

с результатом:

<input>:1: RuntimeWarning: divide by zero encountered in true_divide

array([[inf, inf],
       [inf, inf],
       [inf, inf]])

Здесь NumPy нас лишь предупредил, что встретилось деление на ноль, но расчеты были завершены и все элементы равны inf.

Что это за значение inf? Это сокращение от английского слова infinity – бесконечность. Действительно, при делении на 0 получаем бесконечность. Именно это и указано в значениях элементов массива. Благодаря использованию этого специального значения, NumPy избежал ошибки деления на 0. Причем, inf – это полноценный элемент массивов. Его можно непосредственно задать при определении:

b = np.array([1, 2, np.inf])

И, далее, он может участвовать в вычислениях. Например, умножим b на ноль и посмотрим, что получится:

b*0 # array([ 0.,  0., nan])

Последний элемент превратился в nan. Это еще одно сокращение от английского:

not a number (не число)

То есть, значение nan указывает, что в результате арифметической операции третий элемент перестал быть каким-либо числовым значением. Причем, это определение оказывается «прилипчивым». Например, сложим все элементы массива:

c = b*0

получим:

c.sum() # nan

То есть, любые арифметические операции с nan приводят к nan.

Функции isnan и isinf

Так как элементы inf и nan не относятся к числам, то для их идентификации, проверки, что текущий элемент массива принимает одно из этих значений, существуют функции isnan() и isinf(). Они возвращают True, если элемент равен nan и inf и Flase – в противном случае. Посмотрим как можно их использовать в программе. Пусть имеется массив:

b = np.array([1, 2, np.nan, np.inf, -np.inf])

к которому применим эти две функции:

np.isinf(b)  # array([False, False, False,  True,  True])
np.isnan(b)  # array([False, False,  True, False, False])

На выходе имеем массив с булевыми значениями и True стоит на местах inf (при вызове isinf) и nan (при вызове isnan). Далее, используя этот массив можно исключить нечисловые элементы из массива, например, так:

indx = np.isinf(b)
b[~indx]  # array([ 1.,  2., nan])

Здесь исключаются все элементы inf, а операция ~indx инвертирует булевы значения. Аналогично можно отфильтровать значения nan.

Дополнительные функции: isfinite, iscomplex, isreal

Часто, при работе с массивами требуется определить: являются ли его элементы конечными числами. Для этого используется еще одна функция – isfinit():

# для массива b = np.array([1, 2, np.nan, np.inf, -np.inf])
np.isfinite(b) # array([ True,  True, False, False, False])

Соответственно, все не числовые элементы помечены как False, а числовые – как True.

Далее, мы можем уточнять тип числа: комплексное или действительное, с помощью функций iscompex() и isreal(). Например:

a = np.array([1+2j, 3-4j, 5])  # array([1.+2.j, 3.-4.j, 5.+0.j])
np.iscomplex(a) # array([ True,  True, False])

Обратите внимание, несмотря на то, что тип данных у всех элементов массива complex128 (посмотреть можно через a.dtype), последний элемент функция iscomplex() пометила как False, так как мнимая часть равна нулю.

Аналогично работает функция isreal():

np.isreal(a) # array([False, False,  True])

Только теперь True помечены действительные числа, а False – все остальные. Но, применяя эту функцию к массиву b:

np.isreal(b) # array([ True,  True,  True,  True,  True])

получим все значения True. То есть, специальные значения nan и inf отмечаются как действительные.

Функции logical_and, logical_or, logical_not и logical_xor

В NumPy можно выполнять стандартные булевы операции И, ИЛИ, НЕ, исключающее ИЛИ, применительно к данным массивов. Например, зададим два массива так, чтобы попарно элементы образовывали все возможные комбинации:

X = np.array([True, False, True, False])
Y = np.array([True, True, False, False])

И, затем, применим к ним логические операции:

np.logical_and(X, Y) # логическое И: array([ True, False, False, False])
np.logical_or(X, Y) # логическое ИЛИ: array([False,  True, False,  True])
np.logical_not(X) # логическое НЕ: array([False,  True, False,  True])
np.logical_xor(X, Y) # XOR: array([ True,  True,  True,  True])

Получили вполне ожидаемые результаты в соответствии с таблицами истинности этих операций.

Все те же операции можно проводить и с числовыми значениями, полагая, что 0 – это False, а любое другое число – True. Например, два таких массива:

a = np.array([1, 0, 2, 0])
b = np.array([3, 4, 0, 0])

Будут вести себя идентично массивам X, Y при булевых операциях:

np.logical_and(a, b) # array([ True, False, False, False])