Массивы NumPy: инструменты для научных вычислений
Основные понятия и работа с массивами NumPy
Массив NumPy (numpy.ndarray) представляет собой многомерный контейнер для однотипных данных. В отличие от списков Python, массивы NumPy обеспечивают векторные операции, эффективное использование памяти и высокую скорость вычислений. Основное решение для создания массива – функция np.array(). Она принимает итерируемый объект (список, кортеж) и возвращает массив.
import numpy as np
# Одномерный массив
arr1d = np.array([1, 2, 3, 4, 5])
print(arr1d) # [1 2 3 4 5]
# Двумерный массив (матрица)
arr2d = np.array([[1, 2], [3, 4], [5, 6]])
print(arr2d)
# [[1 2]
# [3 4]
# [5 6]]
# Атрибуты массива
print(f"Форма: {arr2d.shape}, Размерность: {arr2d.ndim}, Число элементов: {arr2d.size}, Тип данных: {arr2d.dtype}")
# Форма: (3, 2), Размерность: 2, Число элементов: 6, Тип данных: int64Python numpy array (массив numpy в python)
Индексация и срезы: доступ к элементам осуществляется как в обычных списках, но для многомерных массивов используется синтаксис с запятыми.
arr = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
print(arr[0, 1]) # 20
print(arr[:, 1]) # [20 50 80] (второй столбец)
print(arr[1:, :2]) # [[40 50]
# [70 80]]
Типичная ошибка: передача списка с элементами разных типов приводит к автоматическому приведению к наиболее общему типу (например, int и float → float). Чтобы избежать неожиданностей, следует явно задавать dtype.
arr_mixed = np.array([1, 2.5, 3]) # массив будет float64
print(arr_mixed.dtype) # float64
Как создать массив, состоящий только из нулей?
Функция np.zeros(shape, dtype=float) создаёт массив заданной формы, заполненный нулями. Часто используется для резервирования памяти или инициализации переменных.
zeros = np.zeros((3, 4), dtype=int)
print(zeros)
# [[0 0 0 0]
# [0 0 0 0]
# [0 0 0 0]]
Проблема: если передать отрицательный размер, возникает ошибка ValueError. Также важно помнить, что np.zeros возвращает новый массив, а не ссылку на существующий.
Как получить массив, заполненный единицами?
np.ones(shape, dtype=None) работает аналогично np.zeros, но заполняет массив единицами. Применяется для создания масок или начальных значений в алгоритмах.
ones = np.ones((2, 3))
print(ones)
# [[1. 1. 1.]
# [1. 1. 1.]]
Ошибка: если не указать dtype, по умолчанию будет float64. Для целочисленных единиц нужно явно указать dtype=int.
Как заполнить массив произвольным числом?
np.full(shape, fill_value, dtype=None) создаёт массив, все элементы которого равны заданному значению fill_value.
full_arr = np.full((2, 2), 3.14, dtype=float)
print(full_arr)
# [[3.14 3.14]
# [3.14 3.14]]
Как создать последовательность чисел с постоянным шагом?
np.arange(start, stop, step, dtype=None) возвращает одномерный массив равномерно расположенных значений от start (включительно) до stop (исключительно) с шагом step. Аналог встроенной range(), но для массивов.
arr = np.arange(0, 10, 2) # [0 2 4 6 8]
print(arr)
# С плавающей точкой
arr_float = np.arange(0.0, 1.0, 0.2)
print(arr_float) # [0. 0.2 0.4 0.6 0.8]
Типичная ошибка: при использовании дробного шага возможны проблемы с округлением. Например, np.arange(0, 1, 0.1) может выдать массив с 10 элементами, но иногда из-за погрешностей число элементов оказывается меньше. Рекомендуется использовать np.linspace для контролируемого количества точек.
Как создать последовательность с заданным количеством точек?
np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None) создаёт массив из num равноотстоящих значений от start до stop (включая stop, если endpoint=True). Полезно для построения графиков или равномерной дискретизации.
lin = np.linspace(0, 1, 5) # [0. 0.25 0.5 0.75 1. ]
print(lin)
# Если endpoint=False, stop не включается
lin2 = np.linspace(0, 1, 5, endpoint=False) # [0. 0.2 0.4 0.6 0.8]
print(lin2)
Внимание: при retstep=True функция возвращает кортеж (массив, шаг). Это удобно, если нужно знать расстояние между точками.
Как создать единичную матрицу?
np.eye(N, M=None, k=0, dtype=float) возвращает двумерный массив с единицами на главной диагонали и нулями в остальных позициях. Параметр k позволяет сдвинуть диагональ.
eye = np.eye(3, dtype=int)
print(eye)
# [[1 0 0]
# [0 1 0]
# [0 0 1]]
# Сдвинутая диагональ
eye_shift = np.eye(4, k=1, dtype=int)
print(eye_shift)
# [[0 1 0 0]
# [0 0 1 0]
# [0 0 0 1]
# [0 0 0 0]]
Как создать массив со случайными числами?
NumPy предоставляет несколько функций в модуле np.random:
np.random.rand(d0, d1, ..., dn)– равномерное распределение на [0, 1).np.random.randn(d0, d1, ..., dn)– стандартное нормальное распределение.np.random.randint(low, high=None, size=None, dtype=int)– случайные целые числа из диапазона.
# Равномерное распределение
uniform = np.random.rand(2, 3)
print(uniform)
# [[0.5488 0.7152 0.6028]
# [0.5449 0.4237 0.6459]]
# Нормальное распределение
normal = np.random.randn(3)
print(normal) # [-0.5187 0.7264 -1.3015]
# Случайные целые
ints = np.random.randint(0, 10, size=(2, 2))
print(ints)
# [[9 3]
# [4 7]]
Ошибка: если не указать size в randint, возвращается одно число, а не массив. Для воспроизводимости результатов необходимо установить seed: np.random.seed(42).
Общие проблемы и их решения
Проблема копирования vs просмотра (view): срезы массива возвращают ссылку на исходные данные (view), а не копию. Изменение view меняет исходный массив. Для получения независимой копии используйте .copy().
arr = np.array([1, 2, 3, 4])
view = arr[1:3]
view[0] = 99
print(arr) # [ 1 99 3 4] – исходный изменился!
# Копия
copy = arr[1:3].copy()
copy[0] = 0
print(arr) # [ 1 99 3 4] – без изменений
Изменение типа данных: arr.astype(new_dtype) создаёт копию с указанным типом. Нельзя изменить тип на месте.
float_arr = np.array([1.5, 2.7])
int_arr = float_arr.astype(int)
print(int_arr) # [1 2] – дробная часть отбрасывается
Ошибки совместимости форм (broadcasting): при арифметических операциях формы должны совпадать или быть расширяемыми. Если форма несовместима, возникает ValueError.
a = np.ones((3, 2))
b = np.ones((2,)) # можно расширить до (1,2), а потом до (3,2) – работает
c = a + b
print(c.shape) # (3, 2)
# Ошибка
b_bad = np.ones((3,))
# a + b_bad # ValueError: operands could not be broadcast together
Индексация вне границ: приводит к IndexError. Всегда проверяйте размеры по атрибутам shape.
Продвинутые примеры использования массивов NumPy
В этом разделе представлены расширенные операции с массивами: преобразование формы, объединение, разделение, универсальные функции, индексация и работа с файлами.
Изменение формы массива (reshape)
reshape(new_shape) возвращает новый вид на те же данные с другой формой. Должен сохраняться общий размер элементов.
arr = np.arange(12)
reshaped = arr.reshape(3, 4)
print(reshaped)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# Ошибка: размер не совпадает
# arr.reshape(3, 5) # ValueError
[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]]
Объединение массивов (concatenate, vstack, hstack)
Функции np.concatenate, np.vstack (вертикально) и np.hstack (горизонтально) объединяют несколько массивов.
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
# Вертикальное объединение (строки)
vert = np.vstack((a, b))
print(vert)
# [[1 2]
# [3 4]
# [5 6]]
# Горизонтальное объединение (столбцы)
c = np.array([[7], [8]])
horiz = np.hstack((a, c))
print(horiz)
# [[1 2 7]
# [3 4 8]]
# concatenate с указанием оси
con = np.concatenate((a, b), axis=0) # то же, что vstack
print(con)
[[1 2] [3 4] [5 6]] [[1 2 7] [3 4 8]] [[1 2] [3 4] [5 6]]
Разделение массивов (split, vsplit, hsplit)
np.split(ary, indices_or_sections, axis=0) делит массив на несколько подмассивов.
arr = np.arange(10)
parts = np.split(arr, [2, 5]) # разделить на позициях 2 и 5
print(parts)
# [array([0, 1]), array([2, 3, 4]), array([5, 6, 7, 8, 9])]
[array([0, 1]), array([2, 3, 4]), array([5, 6, 7, 8, 9])]
Универсальные функции (ufunc) и операции по оси
Многие математические операции (sum, mean, max, min) можно выполнять вдоль заданной оси.
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print("Сумма по строкам (axis=1):", matrix.sum(axis=1)) # [ 6 15]
print("Среднее по столбцам (axis=0):", matrix.mean(axis=0)) # [2.5 3.5 4.5]
print("Максимум по всем элементам:", matrix.max()) # 6
Сумма по строкам (axis=1): [ 6 15] Среднее по столбцам (axis=0): [2.5 3.5 4.5] Максимум по всем элементам: 6
Булева индексация
Маски из булевых значений позволяют выбирать элементы, удовлетворяющие условию.
arr = np.array([10, 20, 30, 40, 50])
mask = arr > 25
print("Маска:", mask) # [False False True True True]
print("Выбранные элементы:", arr[mask]) # [30 40 50]
# Условная замена
arr[arr < 30] = 0
print(arr) # [ 0 0 30 40 50]
Маска: [False False True True True] Выбранные элементы: [30 40 50] [ 0 0 30 40 50]
Fancy indexing (индексация по целочисленным массивам)
Можно использовать массивы индексов для выборки.
arr = np.array([10, 20, 30, 40, 50])
indices = np.array([1, 3, 4])
print(arr[indices]) # [20 40 50]
# Многомерная fancy indexing
matrix = np.arange(12).reshape(3, 4)
rows = np.array([0, 2])
cols = np.array([1, 3])
print(matrix[rows, cols]) # [1 11] (элементы (0,1) и (2,3))
[20 40 50] [ 1 11]
Использование np.where
np.where(condition, x, y) возвращает элементы из x, если условие истинно, иначе из y. Если задан только condition, возвращает индексы истинных элементов.
arr = np.array([1, -2, 3, -4, 5])
result = np.where(arr > 0, arr, 0)
print(result) # [1 0 3 0 5]
# Только индексы
indices = np.where(arr > 0)
print(indices) # (array([0, 2, 4]),)
[1 0 3 0 5] (array([0, 2, 4]),)
Сортировка и уникальные значения
np.sort(arr) возвращает отсортированную копию, np.unique() – отсортированные уникальные элементы.
arr = np.array([3, 1, 2, 3, 2, 1])
print("Отсортированный:", np.sort(arr)) # [1 1 2 2 3 3]
print("Уникальные:", np.unique(arr)) # [1 2 3]
Отсортированный: [1 1 2 2 3 3] Уникальные: [1 2 3]
Сохранение и загрузка массивов
NumPy поддерживает собственный бинарный формат (.npy) и текстовый (.csv через np.savetxt).
arr = np.array([[1.5, 2.3], [3.7, 4.1]])
# Бинарный формат
np.save('my_array.npy', arr)
loaded = np.load('my_array.npy')
print(loaded)
# Текстовый формат
np.savetxt('my_array.csv', arr, delimiter=',', fmt='%.2f')
loaded_csv = np.loadtxt('my_array.csv', delimiter=',')
print(loaded_csv)
[[1.5 2.3] [3.7 4.1]] [[1.5 2.3] [3.7 4.1]]
Использование broadcasting с разными размерностями
Broadcasting позволяет выполнять операции с массивами разной формы без явного повторения данных.
a = np.array([1, 2, 3]) # shape (3,)
b = np.array([[0], [10], [20]]) # shape (3,1)
result = a + b
print(result)
# [[ 1 2 3]
# [11 12 13]
# [21 22 23]]
[[ 1 2 3] [11 12 13] [21 22 23]]
Примечание: правила broadcasting: размерности сравниваются с конца; если размерность не совпадает и не равна 1, возникает ошибка.