Перейти к содержанию

172 уроков, 13 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 2 из 10 Начальный 40 мин 100 XP

Создание массивов в NumPy: arange, linspace, zeros, ones и другие способы

Все конструкторы NumPy на одной странице: arange против linspace, заготовки zeros и ones, матрица eye — и шпаргалка, когда какой способ брать.

Редакция Питоники

Перед вами задача: проанализировать температуру за 365 дней. Писать триста шестьдесят пять чисел руками? А если нужен массив из миллиона точек для графика? В прошлом уроке про первый массив ndarray мы создавали массивы из готовых списков через np.array — этого мало. У NumPy есть десяток конструкторов, которые генерируют данные за вас: последовательности, заготовки нужной формы, диагональные матрицы. В этом уроке разберём все способы создания массива NumPy и главное — научимся выбирать правильный.

Как создать массив NumPy из списка

Начнём с повторения, потому что np.array — самый частый способ создания массива numpy на практике: данные у вас уже есть, нужно лишь превратить их в ndarray. Работает с кортежами, списками и списками списков — из последних получается двумерный массив.

массивы из списков
import numpy as np

temps = np.array([12.5, 14.0, 11.8, 15.3])      # из плоского списка
week = np.array([[12.5, 14.0],
                 [11.8, 15.3]])                 # из списка списков

print(temps)
print(week)
print(week.shape, week.ndim, week.size)
Вывод
[12.5 14.  11.8 15.3]
[[12.5 14. ]
 [11.8 15.3]]
(2, 2) 2 4

Если данные приходят из чужого кода или файла — это ваш путь. А вот когда массива ещё нет и его нужно породить с нуля, вступают в игру генераторы, и первый из них — np.arange.

np.arange: арифметическая прогрессия с шагом

np.arange(start, stop, step) создаёт целые числа через равный шаг — это брат обычного range, который возвращает ndarray. Правило то же, что и у срезов: граница stop не включается. Аргументов можно передавать один, два или три.

три формы arange
import numpy as np

print(np.arange(5))           # от 0 до 4
print(np.arange(2, 11, 3))    # от 2 до 10 с шагом 3
print(np.arange(10, 0, -2))   # шаг бывает и отрицательным
Вывод
[0 1 2 3 4]
[2 5 8]
[10  8  6  4  2]

Отрицательный шаг стоит запомнить отдельно: np.arange(10, 0, -2) — это обратный отсчёт от десяти до двойки. Так удобно строить обратные индексы и убывающие шкалы. Обратите внимание ещё на выравнивание в печати: NumPy подгоняет колонки по самому широкому числу, поэтому между элементами бывает по два пробела.

np.linspace: N точек между двумя краями

np.linspace(start, stop, num) решает задачу с другой стороны: вы говорите не «какой шаг», а «сколько точек нужно». NumPy сам вычислит шаг, а оба края будут включены — в отличие от arange. Именно linspace используют для осей графиков, сеток координат и любых шкал, где важно точное число точек.

linspace: сколько точек - сам решаете
import numpy as np

print(np.linspace(0, 1, 5))     # пять точек от 0 до 1
print(np.linspace(0, 100, 5))   # шаг NumPy посчитает сам
print(np.linspace(10, 20, 3, dtype=int))  # тип можно задать
Вывод
[0.   0.25 0.5  0.75 1.  ]
[  0.  25.  50.  75. 100.]
[10 15 20]

Чем arange отличается от linspace в NumPy?

Разница arange и linspace — вечный источник путаницы, поэтому сведём её в таблицу. Запомните привязку: arange управляет шагом, linspace — количеством точек.

np.arangenp.linspace
Третий аргументшаг между элементамичисло точек
Правая границане включаетсявключается
Дробный шагненадёжен из-за ошибок floatшаг считается точно
Типичная задачацелые индексы, обратный отсчётоси графиков, сетки, шкалы

Почему arange с шагом 0.1 теряет элементы

Обещанный разбор каприза. Число 0.1 в двоичной системе — бесконечная периодическая дробь, как одна треть в десятичной: компьютер хранит лишь приближение с примерно шестнадцатью знаками. Python показывает это честно, если складывать 0.1 подряд:

0.1 копится с ошибкой
# складываем одну десятую десять раз подряд
x = 0.0
for i in range(1, 11):
    x += 0.1
    print(i, x)
Вывод
1 0.1
2 0.2
3 0.30000000000000004
4 0.4
5 0.5
6 0.6
7 0.7
8 0.7999999999999999
9 0.8999999999999999
10 0.9999999999999999

Уже на третьем шаге вместо 0.3 вышло 0.30000000000000004, а десять сложений дали 0.9999999999999999. Старые версии NumPy строили arange накоплением start += step — хвост уплывал. Современный NumPy считает элементы как start + i * step, но число элементов по-прежнему вычисляется делением (stop - start) / step с округлением вверх — одной лишней единицы на шестнадцатом знаке хватает, чтобы округление съехало.

семь элементов вместо восьми
import numpy as np

a = np.arange(0, 0.7, 0.1)   # ждём числа 0.0, 0.1, ... 0.7
print(len(a))
print(a)

b = np.arange(0, 1.1, 0.1)   # здесь деление "сошлось"
print(len(b))
Вывод
7
[0.  0.1 0.2 0.3 0.4 0.5 0.6]
11

Классический пример, где linspace незаменим, — тригонометрия. Построим таблицу синусов для привычных углов: сначала переведём градусы в радианы функцией np.deg2rad (NumPy считает синусы только в радианах — забытый перевод градусов — источник половины «у меня синус не тот» вопросов на форумах).

градусы -> радианы -> синусы
import numpy as np

degrees = np.array([0, 30, 45, 60, 90], dtype=float)
radians = np.deg2rad(degrees)

print(radians)
print(np.sin(radians))   # синусы знакомых углов
Вывод
[0.         0.52359878 0.78539816 1.04719755 1.57079633]
[0.         0.5        0.70710678 0.8660254  1.        ]

В нижней строке легко узнать таблицу Брадиса: 0.5 — это синус 30 градусов, 0.7071 — сорока пяти, 1.0 — девяноста. Обратный перевод делает np.rad2deg. А если нужны точки именно на отрезке от нуля до двух пи, комбинация np.linspace(0, 2 * np.pi, 10) — самый короткий путь.

zeros, ones и full: массивы-заготовки

Часто массив нужен не с данными, а под данные: буфер под результат вычислений, заглушка для будущих значений, обнулённая таблица счётчиков. Для этого в NumPy есть три конструктора: np.zeros заполняет нулями, np.ones — единицами, np.full — любым заданным значением. Запомните синтаксическую ловушку: размер передаётся одним аргументом — кортежем.

заготовки нужной формы
import numpy as np

print(np.zeros(3))            # вектор из трёх нулей
print(np.zeros((2, 3)))       # матрица 2 на 3 из нулей
print(np.ones(4, dtype=int))  # единицы, но целые
print(np.full((2, 2), 7))     # всё заполняем семёрками
Вывод
[0. 0. 0.]
[[0. 0. 0.]
 [0. 0. 0.]]
[1 1 1 1]
[[7 7]
 [7 7]]

Почему np.zeros(3) даёт нули с точкой — 0.? По умолчанию заготовки создаются в float64: вещественный тип — безопасный дефолт, в него влезают и целые значения, и дробные. Целые счётчики удобно делать сразу: np.zeros(10, dtype=int). Точка после числа в печати означает именно «это float», как мы разбирали в уроке про dtype.

np.empty: быстрый, но с мусором

Есть ещё np.empty((2, 2)) — он выделяет память, но не заполняет её, а отдаёт как есть: внутри окажутся байты, которые там лежали до вас. Работает чуть быстрее zeros на огромных массивах, но значения будут случайным мусором — у автора в браузере это оказались остатки предыдущих вычислений страницы. Практическое правило: используйте np.empty только если следующей же строкой заполните массив целиком. Если сомневаетесь — берите np.zeros, две лишние миллисекунды дешевле ночи отладки.

np.eye: единичная матрица

np.eye(n) строит квадратную матрицу n на n, где на диагонали единицы, а вокруг нули. Она называется единичной и играет роль единицы в матричном умножении: умножить матрицу на единичную — всё равно что ничего не менять. Встречается в линейной алгебре, тестах математического кода и методах вроде регуляризации.

единичная матрица 3 на 3
import numpy as np

print(np.eye(3))
Вывод
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

Родственная функция np.diag(v) ставит ваш массив v на диагональ квадратной матрицы, а если ей передать уже готовую матрицу — наоборот, вытащит диагональ. Пригодится, когда дойдёте до матричных операций в уроке про reshape и оси.

Матрица переходов: eye как заготовка

Где eye встречается за пределами учебников? В матрицах переходов: строка — из какого состояния выходим, столбец — в какое приходим. Так моделируют погоду, очереди, поведение пользователей. Система, которая никогда не меняет состояние, описывается в точности единичной матрицей. Реальную матрицу получают из np.eye, заменяя часть единиц и нулей:

матрица переходов погоды
import numpy as np

# погода: состояния "ясно" и "дождь"
# старт - единичная матрица: из любого состояния остаёмся в нём же
P = np.eye(2)
P[0, 0], P[0, 1] = 0.8, 0.2   # из ясно: 80% ясно, 20% дождь
P[1, 0], P[1, 1] = 0.6, 0.4   # из дождя: 60% ясно, 40% дождь

print(P)
Вывод
[[0.8 0.2]
 [0.6 0.4]]

Каждая строка в сумме даёт единицу: 0.8 + 0.2 и 0.6 + 0.4 — все возможные исходы из одного состояния. Заготовка np.eye(2) страхует от глупых ошибок: матрица гарантированно квадратная, диагональ уже занята «остаюсь на месте», остаётся разлить вероятности по клеткам. Запись P[0, 1] — двумерная индексация, правила те же, что у одномерной, — её разбирает индексация и срезы. Тот же приём «заготовка плюс точечная перезапись» работает и с np.zeros.

Практика: сетка температур и таблица умножения

Соберём конструкторы в два мини-упражнения. Первое — шкала температур: пять опорных точек от 10 до 30 градусов через linspace. Второе — таблица умножения на семь: возьмём arange и умножим весь массив сразу. Третьей строкой превратим числа от 1 до 9 в квадратную матрицу методом reshape — пока просто посмотрите на эффект, подробно его разберём в уроке про reshape и многомерные массивы.

комбинируем конструкторы
import numpy as np

# шкала из пяти точек от 10 до 30 градусов
temps = np.linspace(10, 30, 5)
print(temps)

# таблица умножения на 7 для чисел 1..9
sevens = np.arange(1, 10) * 7
print(sevens)

# те же числа - квадратной матрицей
square = np.arange(1, 10).reshape(3, 3)
print(square)
Вывод
[10. 15. 20. 25. 30.]
[ 7 14 21 28 35 42 49 56 63]
[[1 2 3]
 [4 5 6]
 [7 8 9]]

Строка np.arange(1, 10) * 7 — маленькая демонстрация векторизации, о которой подробно пойдёт речь в четвёртом уроке: одна операция применилась ко всем элементам сразу, без циклов. Заметьте также выравнивание: NumPy увидел двузначные числа и подвинул семёрку вправо.

Какой конструктор выбрать: шпаргалка

Девять способов создания массива numpy из этого урока закрывают девять задач из десяти. Сводная таблица для быстрого выбора:

ЗадачаФункцияПример
Данные уже есть в спискеnp.arraynp.array([1, 2, 3])
Целые числа с шагомnp.arangenp.arange(0, 10, 2)
N равных точек на отрезкеnp.linspacenp.linspace(0, 1, 11)
Массив нулей под буферnp.zerosnp.zeros((3, 4))
Массив единицnp.onesnp.ones(5, dtype=int)
Одно значение вездеnp.fullnp.full((2, 2), 7)
Единичная матрицаnp.eyenp.eye(3)
Матрица с заданной диагональюnp.diagnp.diag([0.8, 0.4])
Быстрая память под заполнениеnp.emptynp.empty((2, 3))

Частые ошибки при создании массивов

Две ошибки вы уже видели в предупреждениях — соберём их рядом, чтобы разобрать тексты дословно. NumPy пишет их довольно откровенно, нужно только привыкнуть читать до конца.

две классические ошибки
import numpy as np

# опечатка: размер передан двумя числами вместо кортежа
try:
    print(np.zeros(2, 3))
except TypeError as e:
    print("TypeError:", e)

# reshape под размер, которого нет
try:
    np.arange(4).reshape(3, 2)
except ValueError as e:
    print("ValueError:", e)
Вывод
TypeError: Cannot interpret '3' as a data type
ValueError: cannot reshape array of size 4 into shape (3,2)

Первая ошибка говорит про тип данных, хотя проблема в скобках: np.zeros(2, 3) воспринял тройку как попытку задать dtype. Вторая честно объясняет арифметику: в массиве 4 элемента, а форма (3, 2) требует шести — «cannot reshape». Читайте такие сообщения как уравнение: числа слева и справа должны сходиться. На эту тему есть отдельная ловушка про -1 в reshape, но ей время придёт в уроке про форму массивов.

Что дальше

Теперь вы умеете создавать массив numpy девятью способами и знаете, чем linspace лучше arange с дробным шагом. Следующий логичный вопрос — как доставать из массива отдельные элементы и куски: индексация и срезы с подвохом в виде view и copy. А после индексации — арифметика массивов, ради которой всё и затевалось: покажем, почему векторизация быстрее циклов в сотню раз.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

import numpy as np

print(np.arange(10, 0, -2))
import numpy as np

print(np.linspace(0, 1, 5))
Проверь себя
0 / 6

1. Чем np.linspace отличается от np.arange?

2. Что выведет print(np.arange(2, 11, 3))?

3. Какой код создаст матрицу 2 на 3 из нулей?

4. Какой dtype у массива np.zeros(3) по умолчанию?

5. Что выведет print(np.linspace(10, 20, 3, dtype=int))?

6. Сколько элементов вернёт np.arange(0, 0.7, 0.1)?

Карточки терминов
Запомнено: 0 / 6
Практика

Создайте массив numbers с целыми числами от 1 до 10 включительно через np.arange, возведите его в квадрат (поэлементно) и выведите результат одной строкой.

practice.py
Вопросы и ответы по уроку

Как создать массив NumPy из списка?

Передайте список конструктору np.array: np.array([1, 2, 3]) вернёт одномерный массив, а np.array([[1, 2], [3, 4]]) — двумерный из списка списков. Если данные придут из внешнего источника, полезно сразу указать dtype, например np.array(values, dtype=float), чтобы одна строка в данных не превратила весь массив в текст.

Чем np.arange отличается от np.linspace?

arange строит числа с заданным шагом и не включает правую границу, а linspace строит заданное число точек на отрезке и включает оба края. Для целых шагов arange удобен и точен; с дробным шагом он ненадёжен из-за ошибок плавающей точки — в таких случаях берите linspace.

Как заполнить массив нулями и единицами в NumPy?

Функциями np.zeros и np.ones, передав размер одним кортежем: np.zeros((3, 4)) даст матрицу 3 на 4 из нулей. По умолчанию тип float64, целые нужны — добавьте dtype=int. Для заполнения произвольным значением есть np.full((2, 2), 7).

Как создать пустой массив NumPy?

np.empty((2, 3)) выделяет память без инициализации — внутри окажется случайный мусор, а не нули. Он чуть быстрее zeros на огромных объёмах, но значения нужно перезаписать до первого чтения. Если нужен «пустой» массив под будущие данные, почти всегда безопаснее np.zeros.

Почему np.arange с дробным шагом возвращает не все числа?

Из-за ошибок плавающей точки: шаг 0.1 не представим в двоичной памяти точно, поэтому число элементов, вычисляемое делением (stop − start) / step, может съехать на единицу. Например, np.arange(0, 0.7, 0.1) возвращает семь чисел вместо восьми. Надёжные альтернативы: целый arange с умножением на шаг (np.arange(7) * 0.1) или np.linspace, который гарантирует обе границы.

Как создать заготовку массива NumPy и заполнить её постепенно?

Создайте заготовку нужной формы через np.zeros((n,)) и перезаписывайте значения по индексам — тот же приём «заготовка плюс точечная перезапись», что и в матрице переходов из этого урока. Добавлять элементы по одному нельзя: размер массива фиксирован после создания, поэтому постепенное заполнение — это запись в заранее выделенную память, а не append.

Понравился урок? Сошлитесь на него

«Целочисленный шаг arange надёжен, дробный — нет.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.