Библиотека NumPy: основы работы с массивами в Python
Основные возможности NumPy
Библиотека NumPy предоставляет эффективные структуры данных и функции для работы с многомерными массивами. Основным объектом является ndarray. Ниже рассматриваются ключевые операции.
Как создать массив NumPy?
Самый универсальный способ создания массива - функция np.array. Она преобразует список, кортеж или другой итерируемый объект в ndarray.
import numpy as np
a = np.array([1, 2, 3, 4, 5])
print(a)
print(type(a))библиотека numpy python (библиотека numpy для python)
[1 2 3 4 5] <class 'numpy.ndarray'>
библиотека pandas python (библиотека pandas для python)
Массив автоматически определяет тип данных. Если элементы разного типа, выбирается наиболее общий.
Как создать массивы заранее определённой формы и заполнить их нулями, единицами или последовательностью чисел?
Для этого применяются специализированные функции:
- np.zeros(shape) - массив из нулей.
- np.ones(shape) - массив из единиц.
- np.arange(start, stop, step) - равномерная последовательность целых чисел.
- np.linspace(start, stop, num) - равномерно распределённые числа с заданным количеством точек.
zeros = np.zeros((2, 3))
ones = np.ones(4)
seq = np.arange(0, 10, 2)
space = np.linspace(0, 1, 5)
print('zeros:', zeros)
print('ones:', ones)
print('seq:', seq)
print('space:', space)
zeros: [[0. 0. 0.] [0. 0. 0.]] ones: [1. 1. 1. 1.] seq: [0 2 4 6 8] space: [0. 0.25 0.5 0.75 1. ]
Типичные ошибки: передача в np.array вложенных списков разной длины приводит к созданию одномерного массива объектов, а не многомерного. Для создания массива из 0 и 1 с целыми числами следует указать dtype=int.
# Ошибочная передача
bad = np.array([1, 2], [3, 4]) # SyntaxError - забыты скобки
good = np.array([[1, 2], [3, 4]]) # Правильно
Как выполнить индексацию и срезы?
Основной способ доступа к элементам - квадратные скобки с указанием индексов. Для многомерных массивов индексы перечисляются через запятую. Срезы работают по правилам Python.
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print('Элемент [1,2]:', arr[1, 2])
print('Первая строка:', arr[0, :])
print('Второй столбец:', arr[:, 1])
print('Подматрица 2x2:', arr[:2, :2])
Элемент [1,2]: 6 Первая строка: [1 2 3] Второй столбец: [2 5 8] Подматрица 2x2: [[1 2] [4 5]]
Как выбрать элементы по условию или произвольному набору индексов?
Булева индексация - создание маски: arr[arr > 5]. Fancy indexing - передача списка индексов: arr[[0, 2], [1, 2]].
arr = np.array([10, 20, 30, 40, 50])
mask = arr > 25
print('Булева маска:', mask)
print('Элементы > 25:', arr[mask])
indices = [0, 3, 4]
print('По индексам:', arr[indices])
Булева маска: [False False True True True] Элементы > 25: [30 40 50] По индексам: [10 40 50]
Возможные проблемы: при булевой индексации массив должен иметь ту же форму, что и исходный, иначе возникает ValueError. Для fancy indexing индексы не должны выходить за границы.
Как выполнять арифметические операции?
Поэлементные операции (сложение, вычитание, умножение, деление) выполняются напрямую над массивами одинаковой формы или с использованием вещания (broadcasting).
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print('Сложение:', a + b)
print('Умножение на число:', a * 10)
print('Степень:', a ** 2)
Сложение: [5 7 9] Умножение на число: [10 20 30] Степень: [1 4 9]
Как выполнить матричное умножение или скалярное произведение?
Используются функции np.dot, np.matmul или оператор @.
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print('np.dot:', np.dot(A, B))
print('@:', A @ B)
np.dot: [[19 22] [43 50]] @: [[19 22] [43 50]]
Ошибки вещания: если формы массивов несовместимы (например, (3,) и (4,)), возникает ValueError: operands could not be broadcast together. Всегда проверяйте размерности через .shape.
Как использовать универсальные функции (ufunc)?
Универсальные функции применяются поэлементно к массиву. Примеры: np.sin, np.cos, np.exp, np.sqrt.
x = np.linspace(0, np.pi, 3)
print('sin(x):', np.sin(x))
print('exp(x):', np.exp([0, 1, 2]))
sin(x): [0.0000000e+00 1.0000000e+00 1.2246468e-16] exp(x): [1. 2.71828183 7.3890561 ]
Как создать собственную универсальную функцию?
Функция np.frompyfunc преобразует пользовательскую функцию в ufunc, работающую с массивами.
def custom(x):
return x**2 + 1 if x > 0 else -x
ufunc_custom = np.frompyfunc(custom, 1, 1)
arr = np.array([-2, 0, 3])
print('Результат:', ufunc_custom(arr))
Результат: [2 0 10]
Однако для высокой производительности лучше использовать векторизованные выражения.
Проблема с типами: ufunc через frompyfunc возвращает массив объектов (dtype=object). Для числовых массивов лучше применять np.vectorize с указанием otypes.
Как решать задачи линейной алгебры?
Подмодуль np.linalg содержит функции для решения систем уравнений, вычисления собственных значений, сингулярного разложения и т.д.
# Решение Ax = b
A = np.array([[2, 1], [1, 1]])
b = np.array([5, 3])
x = np.linalg.solve(A, b)
print('Решение:', x)
print('Проверка:', A @ x)
Решение: [2. 1.] Проверка: [5. 3.]
Как вычислить обратную матрицу или собственные значения?
Функции np.linalg.inv и np.linalg.eig.
M = np.array([[1, 2], [3, 4]])
invM = np.linalg.inv(M)
print('Обратная матрица:')
print(invM)
eigvals, eigvecs = np.linalg.eig(M)
print('Собственные значения:', eigvals)
print('Собственные векторы:')
print(eigvecs)
Обратная матрица: [[-2. 1. ] [ 1.5 -0.5]] Собственные значения: [-0.37228132 5.37228132] Собственные векторы: [[-0.82456484 -0.41597356] [ 0.56576746 -0.90937671]]
Ошибка: при попытке обратить сингулярную матрицу возникает np.linalg.LinAlgError. Всегда проверяйте определитель через np.linalg.det.
Как генерировать случайные числа?
Модуль np.random предоставляет функции для создания выборок из различных распределений.
# Равномерное распределение [0,1)
uniform = np.random.rand(3, 2)
print('Равномерное:')
print(uniform)
# Стандартное нормальное
normal = np.random.randn(4)
print('Нормальное:', normal)
Равномерное: [[0.37454012 0.95071431] [0.73199394 0.59865848] [0.15601864 0.15599452]] Нормальное: [ 0.05808361 -0.42428822 -1.04496123 0.50807724]
Как получить целые случайные числа или воспроизводимые результаты?
np.random.randint для целых, np.random.choice для выборки из массива. Для воспроизводимости устанавливается seed с помощью np.random.seed.
np.random.seed(42)
ints = np.random.randint(0, 10, size=5)
print('Целые:', ints)
sample = np.random.choice(['a', 'b', 'c'], size=3, p=[0.1, 0.2, 0.7])
print('Выборка:', sample)
Целые: [6 3 7 4 6] Выборка: ['c' 'c' 'b']
Типичная ошибка: вызов np.random.seed внутри цикла сбрасывает генератор, что приводит к повторяющимся числам. Устанавливайте seed один раз в начале.
Как сохранять и загружать массивы?
Для бинарного сохранения в собственном формате NumPy используются np.save и np.load. Файл получает расширение .npy.
arr = np.array([[1, 2], [3, 4]])
np.save('matrix.npy', arr)
loaded = np.load('matrix.npy')
print('Загруженный массив:')
print(loaded)
Загруженный массив: [[1 2] [3 4]]
Как сохранить массив в текстовый формат (CSV) и прочитать его обратно?
Функции np.savetxt и np.loadtxt.
np.savetxt('data.csv', arr, delimiter=',')
loaded_csv = np.loadtxt('data.csv', delimiter=',')
print('Из CSV:', loaded_csv)
Из CSV: [[1. 2.] [3. 4.]]
Проблемы: при загрузке текстового файла с пропущенными значениями или разными разделителями возникает ошибка. Рекомендуется использовать genfromtxt с параметрами missing_values.
Дополнительные расширенные примеры
Ниже приведены более сложные примеры использования NumPy, демонстрирующие векторизацию, вещание и продвинутую индексацию.
Пример 1: Векторизация вычислений с вещанием
Вычислим расстояние от каждой точки набора данных до центра, используя broadcasting без циклов.
import numpy as np
# Набор точек (1000 точек, 2 координаты)
points = np.random.randn(1000, 2)
center = np.array([0.5, 0.5])
# Вычисление квадратов разностей по всем осям
diff = points - center # broadcasting (1000,2) - (2,) -> (1000,2)
sq_dist = np.sum(diff**2, axis=1) # сумма вдоль осей координат
distances = np.sqrt(sq_dist)
print('Первые 5 расстояний:', distances[:5])
print('Среднее расстояние:', np.mean(distances))
Первые 5 расстояний: [2.04091869 2.08891257 1.25594823 2.36482933 1.74414421] Среднее расстояние: 1.8107927643186705
Пример 2: Булева индексация и изменение значений
Используется для замены всех отрицательных элементов на ноль и подсчёта положительных.
matrix = np.random.randn(4, 4)
print('Исходная матрица:')
print(matrix)
# Маска отрицательных элементов
neg_mask = matrix < 0
print('Количество отрицательных:', np.sum(neg_mask))
# Замена отрицательных на 0
matrix[neg_mask] = 0
print('После замены:')
print(matrix)
Исходная матрица: [[ 0.05391037 -0.68694083 0.07965616 -0.69308327] [ 0.30919091 -0.26241111 -0.47199352 0.41746327] [-1.0409562 -0.47912438 -0.41263476 -0.12011362] [-1.20713113 0.38424814 -0.28215343 1.11440759]] Количество отрицательных: 10 После замены: [[0.05391037 0. 0.07965616 0. ] [0.30919091 0. 0. 0.41746327] [0. 0. 0. 0. ] [0. 0.38424814 0. 1.11440759]]
Пример 3: Свертка одномерного сигнала с помощью np.convolve
signal = np.array([1, 2, 3, 4, 5])
kernel = np.array([0.2, 0.5, 0.3])
# Полная свертка
conv_full = np.convolve(signal, kernel, mode='full')
print('Полная свертка:', conv_full)
# Свертка с тем же размером (режим 'same')
conv_same = np.convolve(signal, kernel, mode='same')
print('Тот же размер:', conv_same)
Полная свертка: [0.2 0.9 1.9 3. 4. 3.1 1.5] Тот же размер: [0.9 1.9 3. 4. 3.1]
Пример 4: Создание и работа с mask-массивами (np.ma)
Маскированные массивы позволяют игнорировать пропущенные или некорректные данные.
data = np.array([1.0, -999, 3.0, -999, 5.0])
masked = np.ma.masked_where(data == -999, data)
print('Маскированный массив:', masked)
print('Среднее без учета -999:', np.ma.mean(masked))
# Заполнение маскированных значений
filled = masked.filled(0)
print('Заполненный:', filled)
Маскированный массив: [1.0 -- 3.0 -- 5.0] Среднее без учета -999: 3.0 Заполненный: [1. 0. 3. 0. 5.]
Пример 5: Использование np.einsum для сложных тензорных операций
Вычисление следа матричного произведения через суммирование с индексами.
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# Эквивалент np.trace(A @ B)
trace_AB = np.einsum('ij,jk->ik', A, B).diagonal().sum()
trace_einsum = np.einsum('ij,jk->ik', A, B).diagonal().sum()
print('Трасса (стандарт):', np.trace(A @ B))
print('Трасса (einsum):', np.einsum('ij,jk->ik', A, B).diagonal().sum())
Трасса (стандарт): 99 Трасса (einsum): 99