{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Программирование для всех\n", "\n", "*Алла Тамбовцева, НИУ ВШЭ*\n", "\n", "## Краткое введение в массивы NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Массивы NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сегодня мы познакомимся с библиотекой NumPy (сокращение от *Numeric Python*), которая часто используется в задачах, связанных с анализом данных и машинным обучением.\n", "\n", "Чтобы мы смогли на конкретных примерах увидеть, зачем эта библиотека используется, давайте ее импортируем. Если вы уже устанавливали Anaconda, то библиотека NumPy также была установлена на ваш компьютер. Проверим: импортируем библиотеку с сокращенным названием, так часто делают, чтобы не «таскать» за собой в коде длинное название. Сокращение `np` для библиотеки `numpy` – общепринятое, его часто можно увидеть в документации или официальных тьюториалах." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Основным объектом NumPy является *Ndarray* – это n-мерный массив (от *n-dimensional array*), структура данных, которая позволяет хранить набор элементов одного типа: либо целые числа, либо числа с плавающей точкой, либо строки, либо логические значения `True` и `False`. Массивы могут быть одномерными, то есть визуально ничем не отличаться от простого списка значений:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 2, 3, 4])" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.array([0, 2, 3, 4])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А могут быть двумерными, то есть представлять собой таблицу, похожую на вложенный список или «список списков»):" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2],\n", " [1, 0]])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.array([[1, 2], \n", " [1, 0]])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Массивы могут быть и большей размерности (список таблиц или что-то более объемное – вкладывать списки в списки мы можем довольно долго), но на практике они нужны редко. \n", "\n", "Зачем изучать массивы? Во-первых, с массивами гораздо приятнее работать, чем со списками, плюс, они занимают меньше памяти. Во-вторых, особенности массивов позволят нам лучше понять, как устроены столбцы в датафреймах (таблицах с данными), с которыми нам предстоит работать дальше.\n", "\n", "Для того, чтобы увидеть, почему массивы удобнее списков, рассмотрим такую задачу. У нас есть список `money_k`, который содержит некоторые суммы в кнатах (волшебная валюта)." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "money_k = [210, 265, 570, 120, 180, 194]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как получить новый список `money_s`, где те же суммы записаны в сиклях (1 сикль = 29 кнатов)? Либо создать пустой список и заполнить его через цикл for, либо использовать списковые включения (генераторы списков). Пойдем по второму пути:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[7.241379310344827,\n", " 9.137931034482758,\n", " 19.655172413793103,\n", " 4.137931034482759,\n", " 6.206896551724138,\n", " 6.689655172413793]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "money_s = [i/29 for i in money_k] \n", "money_s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вроде бы быстро, но без цикла все равно не обошлось. Поступим проще – сделаем из списка массив:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([210, 265, 570, 120, 180, 194])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Money_k = np.array([210, 265, 570, 120, 180, 194])\n", "Money_k" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А теперь просто разделим его на 29:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 7.24137931, 9.13793103, 19.65517241, 4.13793103, 6.20689655,\n", " 6.68965517])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Money_s = Money_k / 29\n", "Money_s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Почему такое возможно? Потому что подобные операции производятся поэлементно, то есть над каждым элементом массива в отдельности. Такие операции еще назвают *векторизованными*. То же будет работать и для нескольких массивов. Допустим, у нас есть два нюхлера (ниффлера), которые в течение 3 часов собирают монетки:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "Niff_one = np.array([83, 73, 65]) \n", "Niff_two = np.array([34, 56, 40])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посчитаем, сколько они насобирали вместе за каждый час:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([117, 129, 105])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Niff_sum = Niff_one + Niff_two\n", "Niff_sum" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Довольно быстро и удобно! \n", "\n", "**Важно!** Запомните эту особенность массивов, нам она очень пригодится, когда будем работать с датафреймами pandas. Если мы решим сложить столбцы в таблице, они тоже будут складываться поэлементно." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Типы данных в массивах и преобразование типов\n", "\n", "Чуть раньше мы зафиксировали, что массивы могут состоять только из элементов одного типа. Посмотрим, что это за типы:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dtype('int64')" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# integer\n", "Niff_sum.dtype " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dtype('float64')" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ " # float\n", "Money_s.dtype " ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dtype('bool')" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# boolean\n", "YN = np.array([True, False])\n", "YN.dtype " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Числа 64 или 32, дописанные в конце названия типа, зависят от системы (32-битная или 64-битная), на это можно не обращать внимания. А вот на что стоит обратить внимание, так это на то, что после `.dtype` нет круглых скобок. Раньше, когда мы дописывали что-то к объекту после точки, это «что-то» было методом (вспомните методы `.lower()` и `.capitalize()` на строках). Здесь `dtype` – это не метод, а *атрибут* массива, то есть какая-то его характеристика. \n", "\n", "Три типа рассмотрели, остались строки. Создадим массив со строками:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dtype(' 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Неравенство выше было автоматически применено к каждому элементу массива, поэтому мы получили новый массив из `True` и `False`, которые сообщают нам, выполнено ли это условие для конкретного элемента или нет. Как посчитать число игроков, которые заработали больше 0 очков? Посчитать число `True`. А если учесть, что вместо `True` Python видит 1, а вместо `False` – 0? Посчитать сумму всех элементов массива:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(points > 10).sum()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А как получить массив, в котором будут только те элементы `points`, которые удовлетворяют некоторому условию? Записать это условие в квадратных скобках, как раньше мы указывали индекс элемента:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([150, 20, 30, 20])" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "points[points > 10]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Запись выше означает, что из `points` Python должен выбрать те элементы, где `points > 10` возвращает `True`.\n", "\n", "Если условия сложные, то их нужно формулировать с помощью операторов `&` (одновременное выполнение условий) или `|` (хотя бы одно из условий верно). " ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([20, 20])" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "points[(points > 10) & (points < 30)] " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "«Словесные» операторы `and` и `or` здесь не подойдут. Плюс, всегда нужно ставить скобки вокруг каждой части условия, иначе Python начнет «раскручивать» условие со знаков `&` или `|`, что закончится ошибкой:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mpoints\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mpoints\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m10\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0mpoints\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m30\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;31m# пытался сопоставить 10 и массив points\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mValueError\u001b[0m: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()" ] } ], "source": [ "points[points > 10 & points < 30] # пытался сопоставить 10 и массив points" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если нужны индексы элементов, удовлетворяющих условиям, можно воспользоваться методом `where`:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([0, 2, 4, 5]),)" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.where(points > 0)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 2 }