Работа с NumPy: эффективные вычисления и управление данными
Введение в NumPy
NumPy (Numerical Python) - это библиотека для языка Python, предоставляющая поддержку многомерных массивов и матриц, а также набор математических функций для их обработки. Она является основным инструментом для научных вычислений, анализа данных и машинного обучения. В отличие от стандартных списков Python, массивы NumPy занимают меньше памяти и позволяют выполнять векторизованные операции, что значительно ускоряет вычисления.
Как создать одномерный массив и выполнить базовые арифметические операции?
Самый эффективный способ начать работу с NumPy - создать массив из списка с помощью np.array() и сразу применить поэлементные операции.
import numpy as np
a = np.array([1, 2, 3, 4, 5])
b = a * 2 + 1
print(b)машинное обучение с помощью python (машинное обучение)
[ 3 5 7 9 11]
модуль numpy в python (модуль numpy в python)
Пояснение: np.array() преобразует список в массив. Арифметические операции применяются ко всем элементам одновременно. Это основной приём, который заменяет циклы в Python.
Как создать массив с равномерно распределенными значениями?
Для генерации последовательности чисел с фиксированным шагом используется np.arange() или np.linspace().
x = np.arange(0, 10, 2) # от 0 до 9 с шагом 2
y = np.linspace(1, 5, 4) # 4 точки от 1 до 5 включительно
print(x)
print(y)[0 2 4 6 8] [1. 2.33333333 3.66666667 5. ]
Цель: быстро сформировать данные для осей графиков, временные ряды, интервалы.
Типичная ошибка:
Использование np.arange() с дробным шагом может привести к неожиданному количеству элементов из-за погрешности чисел с плавающей точкой. Рекомендуется применять np.linspace() для дробных интервалов, так как она гарантирует точное количество точек.
Как создать массив, заполненный нулями или единицами?
Функции np.zeros() и np.ones() создают массивы заданной формы с константными значениями.
zeros = np.zeros((2, 3)) # матрица 2x3 из нулей
ones = np.ones((4,)) # одномерный массив из четырёх единиц
print(zeros)
print(ones)[[0. 0. 0.] [0. 0. 0.]] [1. 1. 1. 1.]
Используется для инициализации весов в нейронных сетях, резервирования памяти, создания единичных матриц (с помощью np.eye()).
Проблема dtype:
По умолчанию np.zeros() и np.ones() создают массивы с типом float64. Для целочисленных массивов нужно указывать параметр dtype=int.
Как выполнить арифметические операции между массивами разной размерности (broadcasting)?
Broadcasting позволяет NumPy выполнять операции с массивами разной формы, автоматически расширяя меньший массив до размеров большего.
a = np.array([[1,2,3],[4,5,6]]) # 2x3
b = np.array([10,20,30]) # 1x3
c = a + b
print(c)[[11 22 33] [14 25 36]]
Пояснение: массив b 'растягивается' по строкам. Broadcasting применяется, когда размеры совпадают по одному из измерений или одно из измерений равно 1. Это экономит память и упрощает код.
Ошибка несовместимости форм:
Если формы не подходят для broadcasting (например, (3,) и (4,)), возникает исключение ValueError: operands could not be broadcast together. Нужно проверять размеры или явно изменять форму через reshape().
Как получить доступ к элементам или подмассивам через индексацию и срезы?
Индексация массивов NumPy работает так же, как и у списков, но поддерживает многомерные срезы и булевые маски.
arr = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print(arr[1, 2]) # элемент второй строки, третьего столбца
print(arr[:2, 1:3]) # первые две строки, столбцы 1 и 2
print(arr[arr % 2 == 0])# все чётные элементы7 [[2 3] [6 7]] [ 2 4 6 8 10 12]
Цель: выборка строк/столбцов для анализа, фильтрация данных, создание подвыборок.
Нюанс с копированием:
Срезы массивов создают представления (view), а не копии. Изменение среза изменяет исходный массив. Для независимой копии следует использовать .copy().
Расширенные примеры работы с NumPy
Ниже приведены неочевидные, но полезные приёмы, которые часто встречаются в реальных задачах научных вычислений.
Использование случайных чисел для симуляции
Генерация случайных матриц с помощью np.random.
import numpy as np
# Массив 3x4 из равномерного распределения [0,1)
rand_uniform = np.random.rand(3, 4)
# Массив из стандартного нормального распределения
rand_normal = np.random.randn(5)
# Целочисленные случайные числа от 0 до 10
rand_int = np.random.randint(0, 10, size=(2,3))
print('Uniform:\n', rand_uniform)
print('Normal:\n', rand_normal)
print('Integer:\n', rand_int)Uniform: [[0.123 0.456 0.789 0.012] [0.345 0.678 0.901 0.234] [0.567 0.890 0.111 0.222]] Normal: [-0.45 1.23 -0.67 0.89 2.10] Integer: [[3 7 2] [8 1 5]]
Применение: Монте-Карло симуляции, инициализация параметров моделей, генерация тестовых данных.
Матричные операции и линейная алгебра
Умножение матриц, вычисление определителя, собственных значений.
A = np.array([[1,2],[3,4]])
B = np.array([[5,6],[7,8]])
# Произведение матриц
C = np.dot(A, B) # или A @ B
print('A @ B =\n', C)
# Определитель
det = np.linalg.det(A)
print('det(A) =', det)
# Собственные значения и векторы
eigenvals, eigenvecs = np.linalg.eig(A)
print('Eigenvalues:', eigenvals)A @ B = [[19 22] [43 50]] det(A) = -2.0 Eigenvalues: [-0.37228132 5.37228132]
Пояснение: np.linalg предоставляет функции для решения систем уравнений, сингулярного разложения и др.
Проблема точности:
Вычисления с плавающей точкой могут давать маленькие ошибки (например, определитель, близкий к нулю, вместо точного нуля). Для критичных задач рекомендуется использовать модуль np.linalg.svd для устойчивости.
Условная замена элементов с помощью np.where
Функция np.where позволяет заменить значения по условию без циклов.
arr = np.array([1, -2, 3, -4, 5])
# Заменить отрицательные на 0
result = np.where(arr < 0, 0, arr)
print(result)
# Также возвращает индексы элементов, удовлетворяющих условию
indices = np.where(arr < 0)[0]
print('Indices of negatives:', indices)[1 0 3 0 5] Indices of negatives: [1 3]
Используется для обработки выбросов, нормализации и фильтрации.
Эффективная статистика вдоль осей
Методы sum, mean, std, min, max с параметром axis.
data = np.random.randint(0, 100, size=(3, 5))
print('Data:\n', data)
print('Среднее по строкам:', data.mean(axis=1))
print('Сумма по столбцам:', data.sum(axis=0))
print('Максимум в каждой строке:', data.max(axis=1))Data: [[12 45 7 88 23] [34 56 78 9 11] [90 2 45 67 33]] Среднее по строкам: [35. 37.6 47.4] Сумма по столбцам: [136 103 130 164 67] Максимум в каждой строке: [88 78 90]
Агрегации по осям - ключевой приём в анализе табличных данных.
Векторизация пользовательских функций с np.vectorize
Если требуется применить собственную функцию к каждому элементу, np.vectorize создает векторизованную версию.
def my_func(x):
if x > 0:
return x ** 2
else:
return -x
vfunc = np.vectorize(my_func)
arr = np.array([-3, 0, 2, -1, 5])
print(vfunc(arr))[3 0 4 1 25]
Пояснение: np.vectorize не ускоряет вычисления (он всё равно вызывает Python-функцию для каждого элемента), но упрощает синтаксис. Для производительности следует переписать функцию с использованием numpy-операций.
Неэффективность vectorize:
Для больших массивов лучше использовать np.where или булеву индексацию, так как они работают на скорости C.
Сортировка и уникальные значения
Сортировка массивов и поиск уникальных элементов.
arr = np.array([3, 1, 2, 3, 2, 1])
print('Sorted:', np.sort(arr))
print('Unique:', np.unique(arr))
# Сортировка по строкам матрицы
mat = np.array([[2,1],[3,0]])
print('Sort rows:', np.sort(mat, axis=1))Sorted: [1 1 2 2 3 3] Unique: [1 2 3] Sort rows: [[1 2] [0 3]]
Применение: подготовка данных к визуализации, удаление дубликатов, обработка временных рядов.
Работа с нулевыми значениями (NaN)
В реальных данных часто встречаются пропуски. np.nan и функции np.nanmean, np.nanstd игнорируют их.
arr = np.array([1.0, np.nan, 3.0, np.nan, 5.0])
print('Mean ignoring NaN:', np.nanmean(arr))
print('Std ignoring NaN:', np.nanstd(arr))
# Замена NaN на среднее значение
mean_val = np.nanmean(arr)
arr_filled = np.where(np.isnan(arr), mean_val, arr)
print('Filled array:', arr_filled)Mean ignoring NaN: 3.0 Std ignoring NaN: 1.632993161855452 Filled array: [1. 3. 3. 3. 5.]
Обработка пропусков - обязательный этап предварительной обработки данных.