{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Знакомство с NumPy\n", "\n", "Первая сторонняя библиотека, с которой мы познакомимся --- [NumPy](https://numpy.org/) (Numerical Python).\n", "\n", "Библиотека `NumPy` предоставляет доступ к многомерным массивам однородных данных (одного типа, обычно числа), подобным массивам `C/C++`, и к методам их обработки. На самом деле, под капотом эти самые массивы реализованы с помощью динамических массивов `C/C++`, которые в отличие от списков, хранят непосредственно данные, а не указатели на них. \n", "\n", "## Зачем нужен NumPy\n", "Необходимость в специальной библиотеке для работы с большими массивами чисел, возникает из-за скорости работы `python`. За гибкость, выразительность, динамическую типизацию и многие другие достоинства, `python` приходится платить скоростью: код написанные на `python`, практически всегда будет работать медленнее, чем аналогичный код, написанный на `C/C++`. Проще всего заметить на примере циклов. \n", "\n", "```c++\n", "#include \n", "#include \n", "#include \n", "#include \n", "#define N 10000000\n", "\n", "int main(){\n", " // Готовим генератор случайных чисел\n", " std::random_device rd;\n", " std::mt19937 gen(rd());\n", " std::uniform_real_distribution<> dis(-1.0, 1.0);\n", " \n", " // Создаём массивы случайных чисел\n", " std::vector a(N), b(N), c(N);\n", " for(int i=0; i < N; ++i){\n", " a[i] = dis(gen);\n", " b[i] = dis(gen);\n", " }\n", "\t\n", " // Измеряем время сложения векторов\n", " auto t1 = std::chrono::high_resolution_clock::now();\n", " \n", " for(int i = 0; i < N; ++i)\n", " c[i] = a[i] + b[i];\n", " \n", " auto t2 = std::chrono::high_resolution_clock::now();\n", " \n", " auto ms_int = std::chrono::duration_cast(t2 - t1);\n", " std::cout << ms_int.count() << \" ms\";\n", "}\n", "```\n", "\n", "\n", "```python\n", "import time\n", "from random import uniform\n", "\n", "\n", "N = 10000000\n", "\n", "# Создаём списки случайных чисел\n", "a = [uniform(-1, 1) for i in range(N)] # новый синтаксис, список случайных чисел\n", "b = [uniform(-1, 1) for i in range(N)]\n", "c = [0.] * N \n", "\n", "# Измеряем время сложения этих списков\n", "t1 = time.time()\n", "\n", "for i in range(N):\n", " c[i] = a[i] + b[i]\n", " \n", "t2 = time.time()\n", "print(f\"{(t2 - t1) * 1000} ms\") # time() - время в секундах\n", "```\n", "\n", "```{note}\n", "Документацию по модулю `time` можно найти [здесь](https://docs.python.org/3/library/time.html), а по модулю `random` [здесь](https://docs.python.org/3/library/random.html). \n", "```\n", "\n", "\n", "\n", "Два исходных кода выше делают похожие вещи, но на `C/C++` и `python` соответственно. Оба создают массивы случайных чисел и измеряют время, необходимое на их сложение. Я сохранил эти исходные коды в файлы `loops.cpp` и `loops.py` и исполнял этот код на одной и той же машине, следующими командами. Флаг `-O0` сообщает компилятору, что компилировать надо в режиме `debug`, что отключает огромное количество оптимизаций.\n", "\n", "```sh\n", "g++ loops.cpp -O0 -o loops\n", "loops.exe\n", "python loops.py\n", "```\n", "\n", "В среднем, я получил следующие результаты.\n", "|`python`|`C/C++`|\n", "| :---: | :---: |\n", "|2425 ms | 87 ms |\n", "\n", "```{note}\n", "Вообще говоря, на эти цифры может повлиять огромное количество факторов, в том числе и случайных. Тем не менее разница настолько явная, что качественная картина ясна: циклы в `C/C++` работают заметно быстрее, чем в `python`.\n", "```\n", "\n", "\"Среднее\" приложение на python не сталкивается с обработкой больших объемов данных и обычно разница в производительности компенсируется затраченным на реализацию алгоритма временем. Но научные вычисления очень часто представляют собой \"программы по перемолке чисел\" и такое замедление существенно. \n", "\n", "Чтобы совместить скорость компилируемого языка и удобство `python` многие библиотеки поставляются вместе со скомпилированными модулями, написанными на `C/++` (или другом компилируемом языке), а в `python` \"прокидывают\" интерфейс для взаимодействия с ними. \n", "\n", "\n", "## Установка NumPy\n", "\n", "В anaconda numpy установлен по умолчанию, а установить его с помощью `PyPI` можно следующим образом: \n", "\n", "```zsh\n", "python -m pip install numpy\n", "```\n", "\n", "Установив NumPy, чтобы начать им пользоваться, необходимо его импортировать." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Библиотека `NumPy` используется настолько часто и настолько многими, что сложилась традиция импортировать `NumPy` с псевдонимом `np`. Любой программист, увидев где-то в коде выражение вида `np.some_method()` догадается, что вызывается метод `some_method` из библиотеки `numpy` и ему не потребуется искать глазами определение объекта `np`, чтобы понять, что скрывается за этим именем." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## О массивах NumPy. Тип данных, размерность, форма.\n", "\n", "Все элемента массива обязательно одного и того же типа. Этот тип можно узнать по полю `dtype` (data type). Этих типов может быть много, но большинство из них --- стандартные числовые типы `C/C++`: `np.bool8`, `np.int8`, `np.int16`, `np.int32`, `np.int64`, `np.unit8`, `np.unit16`, `np.unit32`, `np.uint64`, `np.float16`, `np.float32`, `np.float64`, `np.complex64`, `np.complex128` ([подробнее про типы](https://numpy.org/doc/stable/user/basics.types.html)). \n", "\n", "```{note}\n", "Целые числа в массивах NumPy введут себя совсем не как тип `int` из `python`. Они всегда занимают определенное количество байт и склонны к переполнению, как в `C/C++`.\n", "```\n", "\n", "Так же в этих массивах можно хранить и другие типы данных, но тогда храниться в массиве будут указатели на них, а не сами объекты и большинство преимуществ массивов `NumPy` спадут на нет.\n", "\n", "Кроме типа данных `dtype` у массива можно спросить его размерность `ndim` и его форму `shape` --- длину массива вдоль каждого измерения. Эти измерения называются осями (`axis` в ед. числе и `axes` в мн. числе). Общее количество элементов в массиве (произведение количеств элементов вдоль каждой из осей) хранится в атрибуте `size`.\n", "\n", "Все массивы должны быть \"прямоугольными\", т.е. не может быть матрицы со строками разных длин. \n", "\n", "![image](/_static/lecture_specific/numpy/ndarrays.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Создание массивов\n", "\n", "Создавать массивы `NumPy` (`ndarray`) можно огромным количеством образом.\n", "Например, из списков (или любых других последовательностей) python. При этом итоговый тип данных в массиве определяется как самый общий, среди присутствующих в списке, если не указан явно." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "array([1., 2., 3., 4.])\n", "float64\n", "1\n", "(4,)\n" ] } ], "source": [ "# вектор (одномерный массив) из np.float64\n", "a1D = np.array([1., 2, 3, 4]) \n", "print(repr(a1D), a1D.dtype, a1D.ndim, a1D.shape, sep=\"\\n\")" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "array([[1, 2],\n", " [3, 4]])\n", "int32\n", "2\n", "(2, 2)\n" ] } ], "source": [ "# матрица (двухмерный массив) из целых чисел\n", "a2D = np.array([[1, 2], [3, 4]]) \n", "print(repr(a2D), a2D.dtype, a2D.ndim, a2D.shape, sep=\"\\n\")" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "array([[[1., 2.],\n", " [3., 4.]],\n", "\n", " [[5., 6.],\n", " [7., 8.]]], dtype=float32)\n", "float32\n", "3\n", "(2, 2, 2)\n" ] } ], "source": [ "# Явно указан тип, тензор третьего ранга (трехмерный массив)\n", "a3D = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=np.float32) \n", "print(repr(a3D), a3D.dtype, a3D.ndim, a3D.shape, sep=\"\\n\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Так же есть ряд встроенных функций, создающих массивы ([array creation](https://numpy.org/doc/stable/user/basics.creation.html)). Каждая из них принимает в качестве опционального аргумента `dtype`.\n", "\n", "|Метод | Аргументы | Описание|\n", "| :---: | --- | :---: |\n", "|[np.arange](https://numpy.org/doc/stable/reference/generated/numpy.arange.html#numpy.arange)| `start`, `stop`, `end` | Аналог `np.array(range)`, но можно использовать `float` в качестве аргументов |\n", "|[np.linspace](https://numpy.org/doc/stable/reference/generated/numpy.linspace.html#numpy.linspace)| `start`, `stop`, `num`, `endpoint=True` | Разбиение отрезка [`start`, `stop`] на `num` отрезков, если передать в качестве `endpoint=False`, то последняя точка не включается |\n", "|[np.zeros](https://numpy.org/doc/stable/reference/generated/numpy.zeros.html#numpy.zeros) | `shape` | Массив заданной формы из нулей |\n", "|[np.ones](https://numpy.org/doc/stable/reference/generated/numpy.ones.html#numpy.ones)| `shape` | Массив заданной формы из единиц |\n", "|[np.random](https://numpy.org/doc/stable/reference/random/generated/numpy.random.Generator.random.html#numpy.random.Generator.random)|| Не метод, а подмодуль, содержащий методы создания массивов случайных значений | \n", "|[np.eye](https://numpy.org/doc/stable/reference/generated/numpy.eye.html#numpy.eye) |`n`, `m` | Если `m` не указан, то квадратная единичная матрица размера `nxn`, иначе нулевая матрица размера `nxm` с единицами на диагонали|\n", "|[np.diag](https://numpy.org/doc/stable/reference/generated/numpy.diag.html#numpy.diag)|`v`, `k=0` | Матрица с вектором `v` на главной диагонали (`k=0`) или на `k`-й диагонали |\n", "|[np.vand](https://numpy.org/doc/stable/reference/generated/numpy.vander.html#numpy.vander)|`x`, `N=None`, `increasing=False`| Матрица Вандермонда из вектора `x`|\n", "\n", "\n", "\n", "Кроме того, существует ряд методов, позволяющих создавать массивы из других массивов.\n", "\n", "|Метод | Описание|\n", "| :--- | ---: |\n", "|[np.hstack](https://numpy.org/doc/stable/reference/generated/numpy.hstack.html#numpy.hstack)| Присоединение массивов вдоль строк|\n", "|[np.vstack](https://numpy.org/doc/stable/reference/generated/numpy.vstack.html#numpy.vstack)| Присоединение массивов вдоль столбцов |\n", "|[np.dstack](https://numpy.org/doc/stable/reference/generated/numpy.dstack.html#numpy.dstack)| Присоединение массивов \"в глубину\" |\n", "|[np.block](https://numpy.org/doc/stable/reference/generated/numpy.hstack.html#numpy.hstack) | Составление массива из блоков |" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[1. 1. 2. 2.]\n", " [1. 1. 2. 2.]\n", " [3. 3. 4. 4.]\n", " [3. 3. 4. 4.]]\n" ] } ], "source": [ "A = np.ones((2, 2))\n", "B = 2 * A\n", "C = 3 * A\n", "D = 4 * A\n", "\n", "print(np.block([[A, B], [C, D]]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Арифметические операции над массивами NumPy\n", "\n", "Все основные арифметические операторы (`+`, `-`, `*`, `/`, `//`, `%`, `**`) перегружены для работы с массивами. Определим скаляры и векторы, чтобы разобраться, какие действия они позволяют выполнять." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "x = np.array([0, 1, 2])\n", "y = np.array([7, 13, 42])\n", "alpha = np.pi \n", "beta = 3\n", "\n", "A = np.eye(3)\n", "B = np.ones((3, 3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если в арифметическом выражении участвуют скаляр и массив, то результатом является массив значений поэлементного применения указанной арифметической операции с скаляром. Т.е., можно, например, умножить массив на число или даже сложить массив с числом." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0. 3.14159265 6.28318531]\n", "[3.14159265 4.14159265 5.14159265]\n", "[3.14159265 2.14159265 1.14159265]\n", "[0.44879895 0.24166097 0.07479983]\n", "[ 2.2281692 4.13802852 13.36901522]\n", "[ 2 4 14]\n", "[1 1 0]\n", "[0 1 8]\n" ] } ], "source": [ "print(alpha * x)\n", "print(alpha + x)\n", "print(alpha - x)\n", "print(alpha / y)\n", "print(y / alpha)\n", "print(y // beta)\n", "print(y % beta)\n", "print(x ** beta)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если в выражении участвует два массива, то они должны быть одинаковой формы, а арифметическая операция применяется поэлементно.\n", "\n", "\n", "```{note}\n", "Важно запомнить, что все арифметические операторы выполняют поэлементное преобразование. Это в каком-то смысле является стандартным поведением python и многие другие действия `NumPy` выполняет поэлементно. Далее рассматривается матричное умножение --- принципиально не поэлементная операция. \n", "```" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 7 14 44]\n", "[ 7 12 40]\n", "[ 0 13 84]\n", "[0. 0.07692308 0.04761905]\n", "[0 0 0]\n", "[0 1 2]\n", "[0 1 0]\n" ] } ], "source": [ "print(x + y)\n", "print(y - x)\n", "print(x * y)\n", "print(x / y)\n", "print(x // y)\n", "print(x % y)\n", "print(x ** y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Матричное умножение\n", "\n", "Оператор `*` совершает поэлементное умножение, в том числе и для для двухмерных массивов. Если хочется вложить в двухмерные массив смысл матрицы из линейной алгебры и умножить их как матрицы, то есть два пути:\n", "\n", "a) Использовать оператор `@`:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[1. 0. 0.]\n", " [0. 1. 0.]\n", " [0. 0. 1.]]\n", "----------\n", "[[1. 1. 1.]\n", " [1. 1. 1.]\n", " [1. 1. 1.]]\n", "----------\n", "[[1. 0. 0.]\n", " [0. 1. 0.]\n", " [0. 0. 1.]]\n", "----------\n", "[[1. 1. 1.]\n", " [1. 1. 1.]\n", " [1. 1. 1.]]\n" ] } ], "source": [ "print(A, B, A * B, A @ B, sep=\"\\n\" + \"-\" * 10 + \"\\n\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "b) Воспользоваться специальным типом `np.matrix` предназначенным для хранения именно двухмерных матриц." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "matrix([[1., 0., 0.],\n", " [0., 1., 0.],\n", " [0., 0., 1.]])\n", "--------------------\n", "matrix([[1., 1., 1.],\n", " [1., 1., 1.],\n", " [1., 1., 1.]])\n", "--------------------\n", "matrix([[1., 1., 1.],\n", " [1., 1., 1.],\n", " [1., 1., 1.]])\n" ] } ], "source": [ "A_matrix = np.matrix(A)\n", "B_matrix = np.matrix(B)\n", "print(repr(A_matrix), repr(B_matrix), repr(A_matrix * B_matrix), sep=\"\\n\" + \"-\" * 20 + \"\\n\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`np.matrix` наследуется от `ndarray` и отличается от него в следующем: \n", " - `np.matrix` обязательно двухмерный массив, а `ndarray` имеет произвольную размерность;\n", " - `*` имеет смысл поэлементного управления для `ndarray` и матричного умножения для `np.matrix`;\n", " - `**` --- поэлементное возведение в степень для `ndarray` и матричное возведение для `np.matrix`;\n", " - `np.matrix.H` и `np.matrix.I` возвращают сопряженную и обратную матрицу;\n", "\n", "\n", "```{warning}\n", "Создатели NumPy рекомендуют использовать `ndarray`, и не дают гарантию, что в одном из будущих обновлений `np.matrix` не пропадут из библиотеки.\n", "```\n", "## Индексация\n", "\n", "Индексация одномерных массивов `ndarray` осуществляется очень похоже на индексацию последовательностей `python` (списков, например)." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1. 2. 3. 4.]\n", "1.0 4.0\n" ] } ], "source": [ "print(a1D)\n", "print(a1D[0], a1D[-1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Индексация многомерных осуществляется несколько с новым синтаксисом." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[1 2]\n", " [3 4]]\n", "----------\n", "[1 2]\n", "----------\n", "4 4\n" ] } ], "source": [ "print(a2D, end=\"\\n\" + \"-\"*10 + \"\\n\")\n", "print(a2D[0], end=\"\\n\" + \"-\"*10 + \"\\n\")\n", "print(a2D[-1, -1], a2D[-1][-1])" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[[1. 2.]\n", " [3. 4.]]\n", "\n", " [[5. 6.]\n", " [7. 8.]]]\n", "----------\n", "[[1. 2.]\n", " [3. 4.]]\n", "----------\n", "[7. 8.] [7. 8.]\n", "----------\n", "8.0\n" ] } ], "source": [ "print(a3D, end=\"\\n\" + \"-\"*10 + \"\\n\")\n", "print(a3D[0], end=\"\\n\" + \"-\"*10 + \"\\n\")\n", "print(a3D[-1, -1], a3D[-1][-1], end=\"\\n\" + \"-\"*10 + \"\\n\")\n", "print(a3D[-1, -1, -1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Изменение формы массива\n", "\n", "Можно изменять размерность и форму массива, но с условием, что общее количество элементов остается (`size`) неизменным.\n", "- [np.reshape](https://numpy.org/doc/stable/reference/generated/numpy.reshape.html#numpy.reshape) возвращает новый объект с измененной формой. \n", "- [numpy.ndarray.resize](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.resize.html#numpy.ndarray.resize) изменяет сам объект.\n", "- [numpy.ravel](https://numpy.org/doc/stable/reference/generated/numpy.ravel.html#numpy.ravel) возвращает новый одномерный массив с теми же данными." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]\n" ] } ], "source": [ "A = np.arange(24)\n", "print(A)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0 1 2 3 4 5]\n", " [ 6 7 8 9 10 11]\n", " [12 13 14 15 16 17]\n", " [18 19 20 21 22 23]]\n" ] } ], "source": [ "B = A.reshape(4, 6)\n", "print(B)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[[ 0 1]\n", " [ 2 3]\n", " [ 4 5]]\n", "\n", " [[ 6 7]\n", " [ 8 9]\n", " [10 11]]\n", "\n", " [[12 13]\n", " [14 15]\n", " [16 17]]\n", "\n", " [[18 19]\n", " [20 21]\n", " [22 23]]]\n" ] } ], "source": [ "C = A.reshape(4, 3, 2)\n", "print(C)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0 1 2 3 4 5 6 7 8 9 10 11]\n", " [12 13 14 15 16 17 18 19 20 21 22 23]]\n" ] } ], "source": [ "A.resize(2, 12)\n", "print(A)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]\n" ] } ], "source": [ "A = np.ravel(A) # или A = A.ravel()\n", "print(A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## View. Разделяемое владение данными и копирование\n", "\n", "На самом деле массивы `ndarray` устроены чуть сложнее. Массивы `ndarray` могут разделять одни и те же данные между собой. Массивы можно разделить на два вида: `base` и `view`. `view` --- `ndarray`, который не имеет собственных данных, а ссылается на данные внутри массива `base`. `base` массив владеет данными, `view` лишь позволяет по-другому на них посмотреть.\n", "\n", "Каждый `ndarray` представляет собой объект, который хранит не только указатель на данные (нечто очень похожее на `C` массив), но еще и метаданные, говорящие массиву, каким образом интерпретировать эти данные.\n", "\n", "Рассмотрим пример. Создадим массив `A` и убедимся, что он владеет своими данными." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0 1 2 3 4 5]\n", "Флаги массива B: \n", " C_CONTIGUOUS : True\n", " F_CONTIGUOUS : True\n", " OWNDATA : True\n", " WRITEABLE : True\n", " ALIGNED : True\n", " WRITEBACKIFCOPY : False\n", " UPDATEIFCOPY : False\n", "\n" ] } ], "source": [ "A = np.arange(6)\n", "print(A)\n", "print(\"Флаги массива A: \\n\", A.flags)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Мы создали массив (`ndarray`) размера 6, который владеет данными `[0, 1, 2, 3, 4, 5]` типа `np.int`. Созданный массив мы связали с именем `A`.\n", "![image](/_static/lecture_specific/numpy/base_a.svg)\n", "Теперь создадим новый массив `B` путем изменения формы массива `A` методом `reshape`. Убедимся, что он использует те же данные." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[0 1 2]\n", " [3 4 5]]\n", "Массив B пользуется данными массива A? True\n", "Флаги массива B: \n", " C_CONTIGUOUS : True\n", " F_CONTIGUOUS : False\n", " OWNDATA : False\n", " WRITEABLE : True\n", " ALIGNED : True\n", " WRITEBACKIFCOPY : False\n", " UPDATEIFCOPY : False\n", "\n" ] } ], "source": [ "B = A.reshape(2, 3)\n", "print(B)\n", "print(\"Массив B пользуется данными массива A?\", B.base is A)\n", "print(\"Флаги массива B: \\n\", B.flags)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, массив содержит те же числа, но расположенные уже по строкам матрицы размера $2 \\times 3$. \n", "Напротив флага `\"OWNSDATA\"` стоит `False`, т.е. данными он не владеет. Картинка ниже объясняет, что произошло. Создался новый объект с новыми метаданными, который ссылается на тот же массив с данными. Эти новые метаданные указывают, что массив данных следует интерпретировать так, как будто в нём хранится матрица размера $2 \\times 3$, записанная по строкам друг за другом.\n", "\n", "![image](/_static/lecture_specific/numpy/view_b.svg)\n", "\n", "\n", "Создадим транспонированную матрицу методом `transpose` и убедимся, что она тоже использует данные исходного массива `A`." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[0 3]\n", " [1 4]\n", " [2 5]]\n", "Массив C пользуется данными массива B? False\n", "Массив C пользуется данными массива A? True\n" ] } ], "source": [ "C = B.transpose()\n", "print(C)\n", "print(\"Массив C пользуется данными массива A?\", C.base is A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Матрица `C` --- транспонирование матрицы `B`. Под неё тоже не выделился новый массив с данными, а только лишь новые метаданные.\n", "В данном случае, метаданные указывают, что в массиве хранится матрица размера $3 \\times 2$, которая расположена в памяти таким образом, что первый индекс меняется быстрее второго (т.е. как бы по столбцам).\n", "\n", "![image](/_static/lecture_specific/numpy/view_c.svg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Продемонстрируем, что изменение элементов любого из этих массивов повлияет на остальные." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0 1 2 3 4 42]\n", "---------------\n", "[[ 0 1 2]\n", " [ 3 4 42]]\n", "---------------\n", "[[ 0 3]\n", " [ 1 4]\n", " [ 2 42]]\n" ] } ], "source": [ "C[2, 1] = 42\n", "print(A, B, C, sep=\"\\n\" + \"-\"*15 + \"\\n\")" ] } ], "metadata": { "interpreter": { "hash": "8617202e12f254480e1fae3258716b685f1a56bcbf234a446366b4fd3345ed22" }, "kernelspec": { "display_name": "Python 3.8.10 64-bit", "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.8.10" } }, "nbformat": 4, "nbformat_minor": 2 }