однострочники пайтон. Однострочники Python лаконичный и содержательный код by Кристи. Однострочники
Скачать 4.44 Mb.
|
ПРИМЕЧАНИЕ При повышении размерности массива (например, при переходе от дву- мерного к трехмерному массиву) новая ось становится осью 0, а i-я ось массива более низкой размерности становится (i + 1)-й осью массива более высокой размерности. Работа с массивами NumPy: срезы, транслирование и типы массивов 81 В листинге 3.9 мы выводим атрибуты shape тех же самых массивов из ли- стинга 3.8. Листинг 3.9. Формы одномерного, двумерного и трехмерного массивов NumPy import numpy as np a = np.array([1, 2, 3, 4]) print(a) """ [1 2 3 4] """ print(a.shape) # (4,) b = np.array([[2, 1, 2], [3, 2, 3], [4, 3, 4]]) print(b) """ [[2 1 2] [3 2 3] [4 3 4]] """ print(b.shape) # (3, 3) c = np.array([[[1, 2, 3], [2, 3, 4], [3, 4, 5]], [[1, 2, 4], [2, 3, 5], [3, 4, 6]]]) print(c) """ [[[1 2 3] [2 3 4] [3 4 5]] [[1 2 4] [2 3 5] [3 4 6]]] """ print(c.shape) # (2, 3, 3) Как видите, атрибуты shape содержат намного больше информации, чем атрибуты ndim . Каждый атрибут shape представляет собой кортеж с числом элементов по каждой из осей координат: массив a — одномерный, так что его кортеж shape содержит один эле- мент, отражающий количество столбцов (четыре элемента); массив b — двумерный, поэтому его кортеж shape содержит два эле- мента, перечисляющих количество столбцов и строк; 82 Глава 3. Наука о данных массив c — трехмерный, так что его кортеж shape содержит три эле- мента, по одному для каждой оси. Ось 0 содержит два элемента (каж- дый элемент — двумерный массив), ось 1 — три элемента (каждый из которых — одномерный массив), а ось 2 — три элемента (каждый из которых — целочисленное значение). Теперь, когда вы разобрались с атрибутом shape , вам будет легче понять общую идею транслирования: приведение двух массивов к одной форме путем переупорядочивания их элементов. Посмотрим, как происходит транслирование. Оно автоматически исправляет поэлементные операции над массивами NumPy различной формы. Например, оператор умножения * , примененный к массивам NumPy, обычно выполняет поэлементное умноже- ние. Но что произойдет, если данные слева и справа от него не совпадают по форме (скажем, операнд слева представляет собой массив NumPy, а справа — значение с плавающей точкой)? В этом случае NumPy не выдаст ошибку, а автоматически создаст новый массив из данных, расположенных справа. Размер и размерность этого нового массива — те же, что и у массива слева, он содержит те же значения с плавающей точкой. Таким образом, транслирование представляет собой преобразование массива низкой размерности в массив более высокой размерности для осуществления поэлементных операций. Однородные значения Массивы NumPy — однородны (homogenous), то есть типы данных у всех значений массива одинаковы. Ниже представлен неполный список воз- можных типов данных массивов: bool — булев тип данных в Python (1 байт); int — целочисленный тип данных в Python (размер по умолчанию — 4 или 8 байт); float — тип данных с плавающей точкой в Python (размер по умол- чанию — 8 байт); complex — тип данных для комплексных чисел в Python (размер по умолчанию — 16 байт); np.int8 — целочисленный тип данных (1 байт); np.int16 — целочисленный тип данных (2 байта); Работа с массивами NumPy: срезы, транслирование и типы массивов 83 np.int32 — целочисленный тип данных (4 байта); np.int64 — целочисленный тип данных (8 байт); np.float16 — тип данных с плавающей точкой (2 байта); np.float32 — тип данных с плавающей точкой (4 байта); np.float64 — тип данных с плавающей точкой (8 байт). В листинге 3.10 показано создание массивов NumPy с различными типами. Листинг 3.10. Массивы NumPy с различными типами import numpy as np a = np.array([1, 2, 3, 4], dtype=np.int16) print(a) # [1 2 3 4] print(a.dtype) # int16 b = np.array([1, 2, 3, 4], dtype=np.float64) print(b) # [1. 2. 3. 4.] print(b.dtype) # float64 В этом коде два массива, a и b . Тип данных первого из них — np.int16. Числа в этом массиве — типа integer (после числа нет десятичной точки). Если точнее, то результат вывода свойства dtype массива a — int16. Тип данных второго массива — np.float64. Так что даже если создать массив, в основе которого лежит список целых чисел, NumPy все равно преобразует его тип в np.float64. Из вышеизложенного можно сделать два важных вывода: NumPy позволяет управлять типом данных, причем типы данных массивов NumPy — одно- родны. Код Итак, имеются данные по многим специальностям, и нужно повышать зарплаты одних только исследователей данных на 10 % раз в два года. Со- ответствующий код приведен в листинге 3.11. Задумайтесь на минуту о результатах выполнения этого фрагмента кода. Как вы думаете, что изменится? Какой тип данных будет у полученного в результате массива? Что выведет данный код? 84 Глава 3. Наука о данных Листинг 3.11. Однострочное решение, использующее срезы и присваивания срезам ## Зависимости import numpy as np ## Данные: годовые зарплаты в тысячах долларов (за 2025, 2026 и 2027 гг.) dataScientist = [130, 132, 137] productManager = [127, 140, 145] designer = [118, 118, 127] softwareEngineer = [129, 131, 137] employees = np.array([dataScientist, productManager, designer, softwareEngineer]) ## Однострочник employees[0,::2] = employees[0,::2] * 1.1 ## Результат print(employees) Принцип работы В этом фрагменте кода вы попадаете в 2024 год. Во-первых, вы создаете мас- сив NumPy, каждая строка которого содержит ожидаемую годовую зарплату одного специалиста (исследователя данных, руководителя по программ- ному продукту, дизайнера или разработчика программного обеспечения). А каждый столбец отражает соответствующие годовые зарплаты за 2025, 2026 и 2027 годы. Полученный в результате массив NumPy включает четыре строки и три столбца. Вы нашли средства, чтобы поощрить самых важных специалистов в компании. Вы верите в будущее data science, поэтому решили вознаградить незаметных героев вашей компании: исследователей данных. Вам нужно обновить массив NumPy так, чтобы только зарплаты исследователей данных возрастали на 10 % через год (без капитализации процентов) начиная с 2025 года. Вы разработали следующий замечательный однострочник: employees[0,::2] = employees[0,::2] * 1.1 Он выглядит просто и аккуратно, а результаты его работы — следующие: [[143 132 150] [127 140 145] Работа с массивами NumPy: срезы, транслирование и типы массивов 85 [118 118 127] [129 131 137]] Несмотря на свою простоту, однострочник использует три интересных про- двинутых понятия. Срезы Во-первых, мы воспользуемся срезами и присваиванием срезам. В этом примере с помощью среза мы извлечем каждое второе значение из первой строки массива NumPy employees . Далее выполним некоторые модификации и обновим каждое второе значение первой строки с помощью присваива- ния срезу. Синтаксис присваивания срезу не отличается от самого среза, за исключением одного важного нюанса: в этом случае срез указывается с левой стороны оператора присваивания. Соответствующие элементы бу- дут заменены элементами, указанными справа от оператора присваивания. В представленном фрагменте кода мы заменяем содержимое первой строки массива NumPy обновленными данными о зарплатах. Транслирование Во-вторых, мы воспользуемся транслированием для автоматического ис- правления поэлементных операций над массивами NumPy различной формы. В нашем однострочнике левый операнд — массив NumPy, а правый — значение с плавающей точкой. Опять же, NumPy автоматически создает новый массив того же размера и размерности, что и массив слева от оператора присваива- ния, и заполняет его, по сути, копиями этого значения с плавающей точкой. Фактически NumPy производит вычисление наподобие следующего: np.array([130 137]) * np.array([1.1, 1.1]) Типы массивов В-третьих, наверное, вы поняли, что тип данных результата — не float, а integer, даже если вы выполняете операции над числами с плавающей точкой. При создании массива NumPy понимает, что тот содержит толь- ко целочисленные значения, поэтому полагает, что массив должен быть типа integer. Никакие операции над массивом типа integer не меняют типа данных, вследствие чего NumPy будет округлять значения до целочислен- ных. Опять же, узнать тип массива можно с помощью свойства dtype : 86 Глава 3. Наука о данных print(employees.dtype) # int32 employees[0,::2] = employees[0,::2] * 1.1 print(employees.dtype) # int32 Итак, вы узнали о срезах, присваивании срезам, транслировании и типах массивов NumPy — впечатляющее достижение для однострочного фрагмента кода. Закрепим достигнутый успех, решив небольшую, но практичную за- дачу исследования данных: выявление аномальных значений в измерениях загрязнений для различных городов. Обнаружение аномальных значений с помощью условного поиска по массиву, фильтрации и транслирования В этом однострочнике мы будем изучать данные о загрязненности воздуха в городах. А именно, по представленному двумерному массиву NumPy с дан- ными измерений загрязнений (столбцы) для нескольких городов (строки) мы найдем города с загрязнением выше среднего. Полученные при чтении этого раздела навыки пригодятся вам при поиске аномальных значений в различных наборах данных. Общее описание Индекс качества воздуха (Air Quality Index, AQI) служит для оценки опас- ности вредного воздействия на здоровье и часто применяется для сравнения качества воздуха в различных городах. В следующем однострочнике мы будем исследовать AQI четырех городов: Гонконга, Нью-Йорка, Берлина и Монреаля. Данный однострочник выявляет города, загрязненные выше среднего, то есть такие, максимальное значение AQI которых выше общего среднего значения по всем измерениям всех городов. Важная составляющая нашего решения: поиск элементов в массиве NumPy, удовлетворяющих заданному условию. Это распространенная задача в data science, с которой вы будете сталкиваться очень часто. Обнаружение аномальных значений с помощью условного поиска по массиву 87 Итак, разберемся, как найти элементы массива, удовлетворяющие опреде- ленному условию. 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 88 Глава 3. Наука о данных производит поэлементное сравнение всех целочисленных значений со зна- чением 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 выше среднего: Обнаружение аномальных значений с помощью условного поиска по массиву 89 ## Однострочник 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 выше среднего, и ничего больше, поэтому нас интересуют только индексы строк, которыми 90 Глава 3. Наука о данных мы можем воспользоваться для извлечения символьных названий городов из нашего строкового массива с помощью расширенного доступа по индексу (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. УПРАЖНЕНИЕ 3.1 Вернитесь к примеру с налогообложением в разделе «Простейшие операции с двумерными массивами » на с. 72 и извлеките из матрицы имя сотрудника с самой высокой зарплатой, применив вышеупомянутую методику выборочного булева доступа по индексу. Краткое резюме задачи: как найти человека с максимальным доходом после уплаты налогов в группе людей при заданных годовых зарплатах и ставках на- логообложения? Резюмируя: вы научились использовать булевы выражения для массивов NumPy (опять же, с помощью транслирования) и функцию nonzero() для поиска строк или столбцов, удовлетворяющих определенным условиям. По- занимавшись охраной окружающей среды в этом однострочнике, перейдем к влиятельным блогерам в социальных медиа. Фильтрация двумерных массивов с помощью булева доступа по индексу 91 Фильтрация двумерных массивов с помощью булева доступа по индексу В этом разделе вы закрепите свои навыки доступа к массивам по индексу и транслирования на примере извлечения пользователей Instagram более чем со 100 миллионами подписчиков из небольшого набора данных. А именно, мы выясним имена всех известных блогеров более чем со 100 миллионами подписчиков по заданному двумерному массиву блогеров (строки), в кото- ром первый столбец задает имя блогера в виде строкового значения, а вто- рой — количество подписчиков этого блогера. Общее описание Массивы NumPy расширяют простой тип данных списка дополнительной функциональностью, например многомерными срезами и многомерным до- ступом по индексу. Взгляните на фрагмент кода в листинге 3.16. |