Not a Number — все о NaN / pd 5
В предыдущих разделах вы видели, как легко могут образовываться недостающие данные. В структурах они определяются как значения NaN (Not a Value). Такой тип довольно распространен в анализе данных.
Но pandas спроектирован так, чтобы лучше с ними работать. Дальше вы узнаете, как взаимодействовать с NaN , чтобы избегать возможных проблем. Например, в библиотеке pandas вычисление описательной статистики неявно исключает все значения NaN .
Присваивание значения NaN
Если нужно специально присвоить значение NaN элементу структуры данных, для этого используется np.NaN (или np.nan ) из библиотеки NumPy.
>>> ser = pd.Series([0,1,2,np.NaN,9], ... index=['red','blue','yellow','white','green']) >>> ser red 0.0 blue 1.0 yellow 2.0 white NaN green 9.0 dtype: float64
>>> ser['white'] = None >>> ser red 0.0 blue 1.0 yellow 2.0 white NaN green 9.0 dtype: float64
Фильтрование значений NaN
Есть несколько способов, как можно избавиться от значений NaN во время анализа данных. Это можно делать вручную, удаляя каждый элемент, но такая операция сложная и опасная, к тому же не гарантирует, что вы действительно избавились от всех таких значений. Здесь на помощь приходит функция dropna() .
>>> ser.dropna() red 0.0 blue 1.0 yellow 2.0 green 9.0 dtype: float64
Функцию фильтрации можно выполнить и прямо с помощью notnull() при выборе элементов.
>>> ser[ser.notnull()] red 0.0 blue 1.0 yellow 2.0 green 9.0 dtype: float64
В случае с Dataframe это чуть сложнее. Если использовать функцию pandas dropna() на таком типе объекта, который содержит всего одно значение NaN в колонке или строке, то оно будет удалено.
>>> frame3 = pd.DataFrame([[6,np.nan,6],[np.nan,np.nan,np.nan],[2,np.nan,5]], ... index = ['blue','green','red'], ... columns = ['ball','mug','pen']) >>> frame3
| ball | mug | pen | |
|---|---|---|---|
| blue | 6.0 | NaN | 6.0 |
| green | NaN | NaN | NaN |
| red | 2.0 | NaN | 5.0 |
>>> frame3.dropna() Empty DataFrame Columns: [ball, mug, pen] Index: []
Таким образом чтобы избежать удаления целых строк или колонок нужно использовать параметр how , присвоив ему значение all . Это сообщит функции, чтобы она удаляла только строки или колонки, где все элементы равны NaN .
>>> frame3.dropna(how='all')
| ball | mug | pen | |
|---|---|---|---|
| blue | 6.0 | NaN | 6.0 |
| red | 2.0 | NaN | 5.0 |
Заполнение NaN
Вместо того чтобы отфильтровывать значения NaN в структурах данных, рискуя удалить вместе с ними важные элементы, можно заменять их на другие числа. Для этих целей подойдет fillna() . Она принимает один аргумент — значение, которым нужно заменить NaN .
>>> frame3.fillna(0)
| ball | mug | pen | |
|---|---|---|---|
| blue | 6.0 | 0.0 | 6.0 |
| green | 0.0 | 0.0 | 0.0 |
| red | 2.0 | 0.0 | 5.0 |
Или же NaN можно заменить на разные значения в зависимости от колонки, указывая их и соответствующие значения.
>>> frame3.fillna('ball':1,'mug':0,'pen':99>)
| ball | mug | pen | |
|---|---|---|---|
| blue | 6.0 | 0.0 | 6.0 |
| green | 1.0 | 0.0 | 99.0 |
| red | 2.0 | 0.0 | 5.0 |
Изучаем pandas. Урок 4. Работа с пропусками в данных
![]()
Очень часто большие объемы данных, которые подготавливаются для последующего анализа, имеют пропуски. Для того, чтобы можно было использовать алгоритмы машинного обучения, строящие модели по этим данным, в большинстве случаев, необходимо эти пропуски чем-то и как-то заполнить. На вопрос “чем заполнять?” мы не будем отвечать в рамках данного урока, а вот на вопрос “как заполнять?” ответим.
- pandas и отсутствующие данные
- Замена отсутствующих данных
- Удаление объектов/столбцов с отсутствующими данными
pandas и отсутствующие данные
Для начала, хочется сказать, что в документации по библиотеке pandas есть целый раздел, посвященный данной тематике.
Для наших экспериментов создадим структуру DataFrame , которая будет содержать пропуски. Для этого импортируем необходимые нам библиотеки.
In [1]: import pandas as pd In [2]: from io import StringIO
После этого создадим объект в формате csv . CSV – это один из наиболее простых и распространенных форматов хранения данных, в котором элементы отделяются друг от друга запятыми, более подробно о нем можете прочитать здесь.
In [3]: data = 'price,count,percent\n1,10,\n2,20,51\n3,30,' In [4]: df = pd.read_csv(StringIO(data))
Полученный объект df – это DataFrame с пропусками.
In [5]: df Out[5]: price count percent 0 1 10 NaN 1 2 20 51.0 2 3 30 NaN
В нашем примере, у объектов с индексами 0 и 2 отсутствуют данные в поле percent . Отсутствующие данные помечаются как NaN . Добавим к существующей структуре еще один объект (запись), у которого будет отсутствовать значение в поле count.
In [6]: df.loc[3] = 'price':4, 'count':None, 'percent':26.3> In [7]: df Out[7]: price count percent 0 1.0 10.0 NaN 1 2.0 20.0 51.0 2 3.0 30.0 NaN 3 4.0 NaN 26.3
Для начала обратимся к методам из библиотеки pandas , которые позволяют быстро определить наличие элементов NaN в структурах. Если таблица небольшая, то можно использовать библиотечный метод isnull . Выглядит это так.
In [8]: pd.isnull(df) Out[8]: price count percent 0 False False True 1 False False False 2 False False True 3 False True False
Таким образом мы получаем таблицу того же размера, но на месте реальных данных в ней находятся логические переменные, которые принимают значение False , если значение поля у объекта есть, или True , если значение в данном поле – это NaN . В дополнение к этому можно посмотреть подробную информацию об объекте, для этого можно воспользоваться методом info() .
In [9]: df.info() class 'pandas.core.frame.DataFrame'> Int64Index: 4 entries, 0 to 3 Data columns (total 3 columns): price 4 non-null float64 count 3 non-null float64 percent 2 non-null float64 dtypes: float64(3) memory usage: 128.0 bytes
В нашем примере видно, что объект df имеет три столбца ( count , percent и price ), при этом в столбце price все объекты значимы – не NaN , в столбце count – один NaN объект, в поле percent – два NaN объекта. Можно воспользоваться следующим подходом для получения количества NaN элементов в записях.
In [10]: df.isnull().sum() Out[10]: price 0 count 1 percent 2 dtype: int64
Замена отсутствующих данных
Отсутствующие данные объектов можно заменить на конкретные числовые значения, для этого можно использовать метод fillna() . Для экспериментов будем использовать структуру df , созданную в предыдущем разделе.
In [11]: df.isnull().sum() Out[11]: price 0 count 1 percent 2 dtype: int64 In [12]: df Out[12]: price count percent 0 1.0 10.0 NaN 1 2.0 20.0 51.0 2 3.0 30.0 NaN 3 4.0 NaN 26.3 In [13]: df.fillna(0) Out[13]: price count percent 0 1.0 10.0 0.0 1 2.0 20.0 51.0 2 3.0 30.0 0.0 3 4.0 0.0 26.3
Этот метод не изменяет текущую структуру, он возвращает структуру DataFrame , созданную на базе существующей, с заменой NaN значений на те, что переданы в метод в качестве аргумента. Данные можно заполнить средним значением по столбцу.
In [14]: df.fillna(df.mean()) Out[14]: price count percent 0 1.0 10.0 38.65 1 2.0 20.0 51.00 2 3.0 30.0 38.65 3 4.0 20.0 26.30
В зависимости от задачи используется тот или иной метод заполнения отсутствующих элементов, это может быть нулевое значение, математическое ожидание, медиана и т.п. Для замены NaN элементов на конкретные значения, можно использовать интерполяцию, которая реализована в методе interpolate(), алгоритм интерполяции задается через аргументы метода.
Удаление объектов/столбцов с отсутствующими данными
Довольно часто используемый подход при работе с отсутствующими данными – это удаление записей (строк) или полей (столбцов), в которых встречаются пропуски. Для того, чтобы удалить все объекты, которые содержат значения NaN воспользуйтесь методом dropna() без аргументов.
In [15]: df.dropna() Out[15]: price count percent 1 2.0 20.0 51.0
Вместо записей, можно удалить поля, для этого нужно вызвать метод dropna с аргументом axis=1 .
In [16]: df.dropna() Out[16]: price count percent 1 2.0 20.0 51.0 In [17]: df.dropna(axis=1) Out[17]: price 0 1.0 1 2.0 2 3.0 3 4.0
pandas позволяет задать порог на количество не- NaN элементов. В приведенном ниже примере будут удалены все столбцы, в которых количество не- NaN элементов меньше трех.
In [18]: df.dropna(axis = 1, thresh=3) Out[18]: price count 0 1.0 10.0 1 2.0 20.0 2 3.0 30.0 3 4.0 NaN
P.S.
Раздел: Pandas Python Машинное обучение и анализ данных Метки: Pandas, Python, Машинное обучение
Изучаем pandas. Урок 4. Работа с пропусками в данных : 4 комментария
- Карл 11.04.2018 Полезная статья, решила одну из моих проблем с кодом) Интересно будет узнать, ЧЕМ заполнять пропуски?
- writer 14.04.2018 В начале написал комментарий, частично повторив содержимое статьи))) Чем заполнять, определяется задачей, т.е. если можно заполнить средним значением, то заполняйте средним, если данные чувствительны к таким махинациям, то можно просто их выбросить.
Фильтрация значений и подготовка данных для анализа — Python: Pandas
В библиотеке Pandas реализованы различные подходы, чтобы индексировать элементы. Можно обращаться к ним по порядковому номеру или по метке. При этом есть задачи, в которых нужно отсеивать элементы по условию с использованием специальных масок. В данной задаче указание конкретных интервалов для индексов не всегда подходит.
Для таких задач в Pandas реализован гибкий алгоритм фильтрации. В нем сложность масок определяется только фантазией аналитика и правилами логической арифметики. В этом уроке мы познакомимся с инструментами Pandas для подготовки и первичного анализа данных.
Использование булевых масок
Для работы нам понадобится датасет с недельными показателями кликов на сайтах четырех магазинов:
import pandas as pd df_clicks = pd.read_csv('./data/Cite_clicks_week.csv', index_col=0) print(df_clicks) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 1 1319.0 -265.0 319.0 NaN # 2 NaN 267.0 333.0 344.0 # 3 283.0 NaN 274.0 283.0 # 4 328.0 364.0 328.0 NaN # 5 391.0 355.0 373.0 337.0 # 6 445.0 -418.0 1409.0 445.0 # 7 481.0 NaN 481.0 409.0
Среди показателей есть NaN -значения, которые указывают на пропуски в данных. Также есть отрицательные значения и показатели, которые существенно выше всех остальных. Это вполне обычная ситуация.
Такие значения могут влиять на точность анализа данных и даже на возможность его проведения. Поэтому аналитику приходится находить их и исправлять.
Структура DataFrame поддерживает операции среза последовательных элементов в данных по аналогии со стандартными списками:
print(df_clicks[1:5]) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 2 NaN 267.0 333.0 344.0 # 3 283.0 NaN 274.0 283.0 # 4 328.0 364.0 328.0 NaN # 5 391.0 355.0 373.0 337.0
Чтобы извлечь конкретные строки, можно использовать булевы маски. Это массивы значений True и False . Строка берется, если по ее порядковому номеру в булевой маске стоит значение True :
print(df_clicks[[True, True, False, True, False, True, False]]) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 1 1319.0 -265.0 319.0 NaN # 2 NaN 267.0 333.0 344.0 # 4 328.0 364.0 328.0 NaN # 6 445.0 -418.0 1409.0 445.0
Чтобы извлечь конкретные столбцы в срезе, используется метод loc() , в параметры которого передается срез и массив меток столбцов:
print(df_clicks.loc[1:5, ['SHOP1']]) # => SHOP1 # day # 1 1319.0 # 2 NaN # 3 283.0 # 4 328.0 # 5 391.0 print(df_clicks.loc[1:5, ['SHOP1', 'SHOP3']]) # => SHOP1 SHOP3 # day # 1 1319.0 319.0 # 2 NaN 333.0 # 3 283.0 274.0 # 4 328.0 328.0 # 5 391.0 373.0
Для больших таблиц будет неудобно вручную задавать булевы маски, как мы это делали в примерах выше. В Pandas получить маску можно с помощью логических операторов, примененных к данным:
print(df_clicks['SHOP1'] 300) # => day # 1 False # 2 False # 3 True # 4 False # 5 False # 6 False # 7 False # Name: SHOP1, dtype: bool
Чтобы получить нужные строки согласно булевой маске, достаточно передать ее в качестве индекса в DataFrame :
print(df_clicks[df_clicks['SHOP1'] 300]) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 3 283.0 NaN 274.0 283.0
Выбор нужных столбцов в таблице осуществляется по аналогии с примером выше:
print(df_clicks.loc[df_clicks['SHOP1'] 300, ['SHOP1', 'SHOP3']]) # => SHOP1 SHOP3 # day # 3 283.0 274.0
Поиск пропусков в данных
Одной из существенных проблем в данных являются пропуски. Многие функции агрегации, обработки и даже простые арифметические операции не могут быть выполнены при их наличии. Чтобы обнаружить пропуски, в Pandas используют метод isna() :
print(df_clicks[df_clicks['SHOP1'].isna()]) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 2 NaN 267.0 333.0 344.0
Для поиска строк, которые не содержат пропуски, можно использовать оператор тильда для логического отрицания исходной булевой маски:
print(df_clicks[~df_clicks['SHOP1'].isna()]) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 1 1319.0 -265.0 319.0 NaN # 3 283.0 NaN 274.0 283.0 # 4 328.0 364.0 328.0 NaN # 5 391.0 355.0 373.0 337.0 # 6 445.0 -418.0 1409.0 445.0 # 7 481.0 NaN 481.0 409.0
Данный метод применим не только к столбцам, но и ко всей таблице:
print(df_clicks.isna()) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 1 False False False True # 2 True False False False # 3 False True False False # 4 False False False True # 5 False False False False # 6 False False False False # 7 False True False False
Замена пропусков на определенные значения
Когда мы находим пропуски в данных, это помогает контролировать их качество. Но часто приходится не только их находить, но и исправлять.
В качестве значения, на которое заменяется пропуск, можно взять среднее по всем данным. Чтобы найти среднее, нужно дважды применить метод mean() , поскольку после первого применения получается массив средних для каждого магазина по отдельности:
df_clicks_mean = df_clicks.mean().mean() print(df_clicks_mean) # => 366.94
Чтобы заполнить пропуски, используется метод fillna() с параметром, на который происходит замена пропущенных значений:
print(df_clicks.fillna(df_clicks_mean)) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 1 1319.00000 -265.00000 319.0 366.94881 # 2 366.94881 267.00000 333.0 344.00000 # 3 283.00000 366.94881 274.0 283.00000 # 4 328.00000 364.00000 328.0 366.94881 # 5 391.00000 355.00000 373.0 337.00000 # 6 445.00000 -418.00000 1409.0 445.00000 # 7 481.00000 366.94881 481.0 409.00000
Замена значений в данных согласно условию
Помимо пропусков в данных могут быть значения, которые попали туда по ошибке, были некорректно введены или искажены при записи. Такие случаи называют выбросами.
Анализ данных и поиск выбросов приводит к формированию некоторого условия, которому должны удовлетворять элементы. Если элементы ему не удовлетворяют, то данные значения можно заменить на среднее. Для этого используют метод where() :
print(df_clicks.where(df_clicks 1000, df_clicks_mean)) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 1 366.94881 -265.00000 319.00000 366.94881 # 2 366.94881 267.00000 333.00000 344.00000 # 3 283.00000 366.94881 274.00000 283.00000 # 4 328.00000 364.00000 328.00000 366.94881 # 5 391.00000 355.00000 373.00000 337.00000 # 6 445.00000 -418.00000 366.94881 445.00000 # 7 481.00000 366.94881 481.00000 409.00000
При работе с Pandas это условие формируется в виде булевой маски. Для удобства ее выносят в отдельную переменную:
mask = (0 df_clicks) & (df_clicks 1000) print(mask) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 1 False False True False # 2 False True True True # 3 True False True True # 4 True True True False # 5 True True True True # 6 True False False True # 7 True False True True
print(df_clicks.where( mask, df_clicks_mean, ) ) # => SHOP1 SHOP2 SHOP3 SHOP4 # day # 1 366.94881 366.94881 319.00000 366.94881 # 2 366.94881 267.00000 333.00000 344.00000 # 3 283.00000 366.94881 274.00000 283.00000 # 4 328.00000 364.00000 328.00000 366.94881 # 5 391.00000 355.00000 373.00000 337.00000 # 6 445.00000 366.94881 366.94881 445.00000 # 7 481.00000 366.94881 481.00000 409.00000
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Булевы операции и функции, значения inf и nan
Продолжаем знакомство с операциями над массивами и посмотрим как они ведут себя с булевыми операциями. Предположим, имеется одномерный массив:
a = np.array([1, 2, 3, 10, 20, 30])
и мы хотим определить все числа, которые больше 5. Мы с вами уже выполняли такую операцию и для этого сначала формировали булевый массив, а затем, выделяли элементы, у индексов которых стоит значение True:
indx = a > 5 a[indx]
На выходе получим массив из трех элементов, которым соответствуют позиции True:
Видите, как это может быть удобно: выделить нужные элементы, не используя ни одного оператора цикла языка Python. А, значит, такая конструкция будет работать достаточно быстро (так как внутри реализована на языках Си и Fortran).
Конечно, эту запись можно еще упростить и записать в виде:
a[ a > 5 ]
Результат будет тем же. По аналогии работают и другие булевы операторы:
Здесь в качестве операндов 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
- 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:
с результатом: :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])