Шаг 40.
Однострочники Python. Наука о данных. Обнаружение аномальных значений с помощью условного поиска по массиву, фильтрации и транслирования

    На этом шаге мы будем изучать данные о загрязненности воздуха в городах. А именно, по представленному двумерному массиву NumPy с данными измерений загрязнений (столбцы) для нескольких городов (строки) мы найдем города с загрязнением выше среднего. Полученные при чтении этого раздела навыки пригодятся вам при поиске аномальных значений в различных наборах данных.

Общее описание

    Индекс качества воздуха (Air Quality Index, AQI) служит для оценки опасности вредного воздействия на здоровье и часто применяется для сравнения качества воздуха в различных городах. В следующем однострочнике мы будем исследовать AQI четырех городов: Гонконга, Нью-Йорка, Берлина и Монреаля.

    Данный однострочник выявляет города, загрязненные выше среднего, то есть такие, максимальное значение AQI которых выше общего среднего значения по всем измерениям всех городов.

    Важная составляющая нашего решения: поиск элементов в массиве NumPy, удовлетворяющих заданному условию. Это распространенная задача в data science, с которой вы будете сталкиваться очень часто.

    Итак, разберемся, как найти элементы массива, удовлетворяющие определенному условию. NumPy предоставляет функцию nonzero() для поиска индексов элементов в массиве, которые не равны нулю. Посмотрите на пример 3.12.


Пример 3.12. Функция nonzero
import numpy as np

X = np.array([[1, 0, 0],
              [0, 2, 2],
              [3, 0, 0]])
print(np.nonzero(X))

    Результат представляет собой кортеж из двух массивов NumPy:

(array([0, 1, 1, 2], dtype=int64), array([0, 1, 2, 0], dtype=int64))

    Первый массив содержит индексы строк, а второй - индексы столбцов ненулевых элементов. В исходном двумерном массиве содержится четыре ненулевых элемента: 1, 2, 2 и 3, на позициях X[0,0], X[1,1], X[1,2] и X[2,0].

    Как же с помощью функции nonzero() найти в массиве элементы, удовлетворяющие определенному условию? Для этого обратимся еще к одной замечательной возможности NumPy: булевым операциям над массивами, выполняемым с помощью транслирования (пример 3.13)!


Пример 3.13. Транслирование и поэлементные булевы операторы в NumPy
import numpy as np

X = np.array([[1, 0, 0], [0, 2, 2], [3, 0, 0]])
print(X == 2)
# [[False False False] 
#  [False True True] 
#  [False False False]]

    Транслирование происходит при копировании (по существу) целочисленного значения 2 в новый массив той же формы, что и исходный. Далее NumPy производит поэлементное сравнение всех целочисленных значений со значением 2 и возвращает полученный в результате булев массив.

    В нашем основном коде для поиска элементов, удовлетворяющих определенному условию, мы воспользуемся сочетанием функции nonzero() и операций над булевыми массивами.

Код

    В примере 3.14 мы найдем в наборе данных города с максимумами загрязнения, превышающими среднее значение.


Пример 3.14. Однострочное решение, использующее транслирование, булевы операторы и выборочный доступ по индексу
## Зависимости
import numpy as np

## Данные: измерения индекса качества воздуха, AQI (строка = город)
X = np.array(
    [[42, 40, 41, 43, 44, 43],  # Гонконг
     [30, 31, 29, 29, 29, 30],  # Нью-Йорк
     [8, 13, 31, 11, 11, 9],  # Берлин
     [11, 11, 12, 13, 11, 12]])  # Монреаль
cities = np.array(["Hong Kong", "New York", "Berlin", "Montreal"])

## Однострочник
polluted = set(cities[np.nonzero(X > np.average(X))[0]])

## Результат
print(polluted)
Архив с файлом можно взять здесь.

    Можете определить, какими будут результаты выполнения этого кода?

Принцип работы

    Массив данных X содержит четыре строки (по одной для каждого города) и шесть столбцов (по одному для каждого отрезка измерения - в данном случае дня). Строковый массив cities содержит четыре названия городов в том порядке, в каком те встречаются в массиве с данными.

    Вот однострочник для поиска городов, в которых наблюдается уровень AQI выше среднего:

## Однострочник
polluted = set(cities[np.nonzero(X > np.average(X))[0]])

    Чтобы понять, как он работает в целом, необходимо сначала разобраться в каждой из его составных частей. Проанализируем его, начав изнутри. В его сердцевине находится операция над булевым массивом (пример 3.15).


Пример 3.15. Операция над булевым массивом с помощью транслирования
print(X > np.average(X))
# [[True True True True True True]
# [True True True True True True]
# [False False True False False False]
#[False False False False False False]]

    Чтобы привести оба операнда к одной форме с помощью транслирования, мы воспользовались булевым выражением. Для вычисления среднего по всем элементам нашего массива NumPy значения AQI мы задействуем функцию np.average(). Далее булево выражение производит поэлементное сравнение, и получается булев массив, содержащий True, если соответствующее измерение превышает среднее значение AQI.

    Благодаря генерации этого булева массива мы знаем в точности, какие элементы удовлетворяют условию "выше среднего", а какие - нет.

    Напомним, что значение True языка Python представлено значением 1 типа integer, а False - 0. На самом деле тип объектов True и False - bool - является подклассом int. Таким образом, каждое булево значение является также и целочисленным значением. Благодаря этому мы можем воспользоваться функцией nonzero() для поиска всех удовлетворяющих условию индексов строк и столбцов, вот так:


print(np.nonzero(X > np.average(X)))
# (array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2], dtype=int64), 
   array([0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 2], dtype=int64))

    Получаем два кортежа, первый - с индексами строк ненулевых элементов, а второй - с индексами соответствующих им столбцов.

    Нам нужны только названия городов со значениями AQI выше среднего, и ничего больше, поэтому нас интересуют только индексы строк, которыми мы можем воспользоваться для извлечения символьных названий городов из нашего строкового массива с помощью расширенного доступа по индексу (advanced indexing) - методики доступа по индексу, позволяющей описывать не непрерывную последовательность индексов массива.

    Таким образом можно обращаться к произвольным элементам данного массива NumPy, указывая последовательность либо целых чисел (выбираемых индексов), либо булевых значений (для выбора тех индексов, для которых соответствующее булево значение равно True):


print(cities[np.nonzero(X > np.average(X))[0]])
# ['Hong Kong', 'Hong Kong', 'Hong Kong', 'Hong Kong', 'Hong Kong', 'Hong Kong',
# 'New York', 'New York', 'New York', 'New York', 'New York', 'New York', 'Berlin']

    Как видите, в полученной последовательности строковых значений немало повторов, поскольку в числе измерений AQI Гонконга и Нью-Йорка много значений выше среднего.

    Осталось только убрать эти повторы. Для этого мы преобразуем нашу последовательность во множество Python, в котором по определению отсутствуют дублирующиеся значения, и получим краткую сводку названий всех городов, степень загрязнения воздуха в которых превышает средние значения AQI.

    Резюмируя: вы научились использовать булевы выражения для массивов NumPy (опять же, с помощью транслирования) и функцию nonzero() для поиска строк или столбцов, удовлетворяющих определенным условиям. Позанимавшись охраной окружающей среды в этом однострочнике, перейдем к влиятельным блогерам в социальных медиа.

    На следующем шаге мы рассмотрим фильтрацию двумерных массивов с помощью булева доступа по индексу.




Предыдущий шаг Содержание