Appearance
6. Манипуляции с составом 1D-векторов: изменение структуры, сортировка и расщепление
Вставка, добавление и удаление элементов
Механизм работы функций append, insert и delete в памяти
Поскольку массивы NumPy (ndarray) имеют строго фиксированный размер и хранятся в виде непрерывного блока памяти на C-уровне, физически «расширить» или «ужать» существующий буфер невозможно.
Поэтому функции numpy.append(), numpy.insert() и numpy.delete() работают по следующему принципу: они выделяют в оперативной памяти совершенно новый блок, копируют туда нужные элементы из старого массива, добавляют или удаляют целевые значения и возвращают этот новый массив. Исходный массив всегда остается неизменным.
numpy.append(arr, values)— добавляет элементыvaluesв самый конец массиваarr.numpy.insert(arr, obj, values)— вставляет элементыvaluesперед указанным индексомobj.numpy.delete(arr, obj)— удаляет элементы на позициях, указанных вobj.
python
import numpy
initial_vector = numpy.array([10, 20, 30])
# Appending an element to the end
extended_vector = numpy.append(initial_vector, 40)
# Inserting an element at index 1
inserted_vector = numpy.insert(initial_vector, 1, 99)
# Deleting an element at index 2
reduced_vector = numpy.delete(initial_vector, 2)
print("Original is untouched:", initial_vector) # Outputs: [10 20 30]
print("Appended:", extended_vector) # Outputs: [10 20 30 40]
print("Inserted:", inserted_vector) # Outputs: [10 99 20 30]
print("Deleted:", reduced_vector) # Outputs: [10 20]Алгоритмы сортировки одномерных массивов
Функция модуля numpy.sort() против метода объекта .sort()
В NumPy сортировка числовых векторов может быть выполнена двумя способами, которые принципиально по-разному работают с оперативной памятью:
- Функция модуля
numpy.sort(arr): Принимает массив, создает его копию в памяти, сортирует её по возрастанию на C-скорости и возвращает новый отсортированный массив. Исходные данные не меняются. - Метод объекта
arr.sort(): Выполняет сортировку на месте (in-place). Модифицируются значения прямо внутри исходного буфера памяти. Никаких новых аллокаций памяти не происходит, что критично для экономии RAM при работе со сверхбольшими массивами.
Сортировка по убыванию
В NumPy нет встроенного флага reverse=True для сортировки в обратном порядке. Чтобы отсортировать массив по убыванию, применяется эффективная комбинация: массив сначала сортируется по возрастанию, а затем разворачивается с помощью расширенного среза с отрицательным шагом [::-1].
python
import numpy
unordered_scores = numpy.array([3, 1, 4, 1, 5, 9])
# Sorting in ascending order (creates a new copy)
ascending_copy = numpy.sort(unordered_scores)
# Sorting in descending order via slicing trick
descending_copy = numpy.sort(unordered_scores)[::-1]
print("Ascending copy:", ascending_copy) # Outputs: [1 1 3 4 5 9]
print("Descending copy:", descending_copy) # Outputs: [9 5 4 3 1 1]
# In-place sorting (mutates the original array)
unordered_scores.sort()
print("Original is now sorted:", unordered_scores) # Outputs: [1 1 3 4 5 9]Объединение и расщепление векторов
Объединение массивов через concatenate
Для сборки единых датасетов из изолированных батчей (частей), слияния векторов признаков (feature fusion) или добавления новых данных в существующие последовательности используется функция numpy.concatenate((arr1, arr2, ...)).
Она принимает кортеж или список массивов и склеивает их последовательно в один длинный вектор. Важное правило для одномерных массивов: вы можете объединять сколько угодно векторов любого размера, так как операция происходит вдоль единственной существующей оси.
python
import numpy
# Simulate training logs from two different compute nodes
node_a_losses = numpy.array([0.85, 0.62, 0.41])
node_b_losses = numpy.array([0.35, 0.22])
# Concatenating multiple 1D arrays into a single sequence
combined_history = numpy.concatenate((node_a_losses, node_b_losses))
print(combined_history)
# Outputs a single merged array: [0.85 0.62 0.41 0.35 0.22]Разделение массивов через split и hsplit
Для сегментации данных, разбиения временных рядов или деления последовательностей на изолированные фрагменты используются функции numpy.split(arr, sections) и её эквивалент для одномерного пространства numpy.hsplit(arr, sections) (horizontal split).
Если в параметр sections передается целое число, NumPy пытается расщепить массив на указанное количество абсолютно равных частей. Если массив невозможно разделить поровну (например, массив из 7 элементов попытаться разделить на 3 части), библиотека выбросит критическую ошибку ValueError. Функции возвращают стандартный список Python, состоящий из дочерних массивов ndarray.
python
import numpy
timeline_data = numpy.arange(9)
# Splitting 1D sequence into 3 equal chunks
chunks_list = numpy.split(timeline_data, 3)
print(chunks_list)
# Outputs a list of arrays: [array([0, 1, 2]), array([3, 4, 5]), array([6, 7, 8])]Округление значений с помощью numpy.round
В процессе матричных вычислений, нормализации или генерации случайных весов в NumPy часто получаются длинные дроби с плавающей точкой (из-за ограничений двоичной мантиссы). Для приведения данных к читаемому виду или подготовки признаков к определенному масштабу используется функция numpy.round(arr, decimals=0) (или метод массива .round()).
Особенности банковского округления (Round half to even)
Важнейший нюанс для новичка: NumPy (в соответствии со стандартом IEEE 754) использует так называемое «банковское» или «статистическое» округление. Если дробная часть числа равна ровно 0.5, компьютер округляет его не всегда в большую сторону, а к ближайшему четному числу.
2.5округлится до2.0(так как 2 — четное).3.5округлится до4.0(так как 4 — четное).
Это необходимо для устранения систематической ошибки смещения (bias) при математическом анализе больших массивов данных. Если всегда округлять 0.5 вверх, то среднее значение датасета искусственно вырастет, а банковское округление балансирует этот показатель.
Примеры использования на практике
Пример 1. Базовое округление до целых чисел и заданных знаков
Вы можете управлять точностью округления с помощью параметра decimals. Срез данных происходит без создания циклов на максимальной скорости.
python
import numpy as np
# Simulate a vector of continuous predicted probabilities
predictions = np.array([0.1264, 0.5555, 2.5, 3.5])
# 1. Round to 2 decimal places
rounded_prec = np.round(predictions, decimals=2)
print("Round to 2 decimals:", rounded_prec)
# Outputs: [0.13 0.56 2.5 3.5 ]
# 2. Round to closest integers (default decimals=0)
# Note how 2.5 and 3.5 are rounded according to the "round-half-to-even" rule
rounded_ints = predictions.round()
print("Bankers rounding to ints:", rounded_ints)
# Outputs: [0. 1. 2. 4. ]Пример 2. Округление до десятков и сотен (Отрицательный параметр decimals)
Если передать в decimals отрицательное значение, NumPy начнет округлять целую часть числа (слева от десятичной запятой) до десятков, сотен, тысяч и так далее. Это незаменимо при грубой кластеризации цен или нормировании больших метрик.
python
import numpy as np
# Simulate huge raw financial loss values across iterations
raw_losses = np.array([1234.5, 5678.9, 89.1])
# Round to the nearest hundred (decimals=-2)
coarse_losses = np.round(raw_losses, decimals=-2)
print("Original losses:", raw_losses)
print("Rounded to hundreds:", coarse_losses)
# Outputs: [1200. 5700. 100.]Глобальная альтернатива для отображения: numpy.set_printoptions
Если округление вам нужно только для красивого вывода результатов на экран (в консоль), использовать np.round не стоит. Метод np.round реально изменяет данные в памяти, безвозвратно отбрасывая точность мантиссы.
Если вам нужно сохранить идеальную точность расчетов внутри компьютера, но при этом скрыть «хвосты» дробей в консоли, используйте настройку numpy.set_printoptions(precision=...):
python
import numpy as np
# Long floating-point weights vector
model_weights = np.array([0.14285714, 0.28571429, 0.57142857])
# Set global print precision to 3 decimal places and suppress scientific notation
np.set_printoptions(precision=3, suppress=True)
print("Clean console output:", model_weights)
# Outputs: [0.143 0.286 0.571]
# PROOF: The actual precision in memory remains untouched!
print("Raw element value still exists:", model_weights[0])
# Outputs: 0.14285714Упражнения базовые (5 шт) (только с опорой на объясненную в данном уроке теорию)
1. Расширение лога признаков
Задача: Дан исходный вектор показаний датчика: [1.2, 2.4, 3.1]. Добавьте в конец этого вектора новое значение 5.5 с помощью функции numpy.append(). Выведите полученный измененный массив на экран.
Посмотреть решение
python
import numpy
sensor_logs = numpy.array([1.2, 2.4, 3.1])
# Adding a new data point to the end of the tensor block
updated_logs = numpy.append(sensor_logs, 5.5)
print(updated_logs) # Outputs: [1.2 2.4 3.1 5.5]2. Точечная вставка токена-заполнителя
Задача: Создайте массив индексов токенов: [101, 2003, 1037, 102]. Вставьте ровно в середину массива (перед элементом с индексом 2) специальный токен заполнителя 9999 с помощью numpy.insert(). Выведите результат.
Посмотреть решение
python
import numpy
token_ids = numpy.array([101, 2003, 1037, 102])
# Inserting a special token padding placeholder at position 2
patched_tokens = numpy.insert(token_ids, 2, 9999)
print(patched_tokens) # Outputs: [ 101 2003 9999 1037 102]3. Очистка вектора от поврежденной позиции
Задача: Дан массив вероятностей ответов: [0.85, 0.99, 0.01, 0.45]. Известно, что элемент на позиции с индексом 2 является ложным выбросом. Удалите его с помощью функции numpy.delete() и выведите очищенную последовательность.
Посмотреть решение
python
import numpy
raw_probabilities = numpy.array([0.85, 0.99, 0.01, 0.45])
# Removing the corrupted element at index 2
clean_probabilities = numpy.delete(raw_probabilities, 2)
print(clean_probabilities) # Outputs: [0.85 0.99 0.45]4. Ранжирование метрик потерь на месте
Задача: Создайте массив сырых значений функции потерь: numpy.array([0.65, 0.12, 1.45, 0.02]). Отсортируйте этот массив по возрастанию на месте, без выделения дополнительной памяти, используя метод объекта. Выведите измененный массив.
Посмотреть решение
python
import numpy
loss_records = numpy.array([0.65, 0.12, 1.45, 0.02])
# In-place sort to optimize RAM allocations
loss_records.sort()
print(loss_records) # Outputs: [0.02 0.12 0.65 1.45]5. Расщепление батча временного ряда
Задача: Сгенерируйте последовательный массив из 8 элементов (от 0 до 7) через numpy.arange. Расщепите этот массив на 4 абсолютно равных временных окна с помощью numpy.split(). Выведите полученный список подмассивов на экран.
Посмотреть решение
python
import numpy
time_sequence = numpy.arange(8)
# Splitting into 4 symmetric chunks
sequence_chunks = numpy.split(time_sequence, 4)
print(sequence_chunks)
# Outputs: [array([0, 1]), array([2, 3]), array([4, 5]), array([6, 7])]Упражнения смешанные (5 шт) (включают материал прошлых уроков)
6. Сортировка сгенерированной числовой сетки по убыванию
Задача: Сгенерируйте линейную числовую сетку из 5 элементов в диапазоне от -2.5 до 2.5 включительно (Урок 3). Отсортируйте полученные вещественные значения по убыванию (от большего к меньшему), объединив функцию numpy.sort() и расширенный срез (Урок 5 & Урок 6). Выведите итоговый результат.
Посмотреть решение
python
import numpy
# Step 1: Generating the baseline grid (Lesson 3)
base_grid = numpy.linspace(-2.5, 2.5, 5) # [-2.5, -1.25, 0.0, 1.25, 2.5]
# Step 2: Sorting and fully reversing the layout (Lesson 5 & Lesson 6)
descending_grid = numpy.sort(base_grid)[::-1]
print(descending_grid) # Outputs: [ 2.5 1.25 0. -1.25 -2.5 ]7. Расщепление квантованного целочисленного вектора признаков
Задача: Сгенерируйте упорядоченный вектор из 6 элементов (от 0 до 5) через numpy.arange (Урок 3), принудительно задав ему тип данных numpy.int8 (Урок 4). Расщепите этот квантованный массив на 2 равные части с помощью numpy.hsplit() (Урок 6). Выведите полученные куски и проверьте, сохранили ли элементы свой исходный тип int8.
Посмотреть решение
python
import numpy
# Generating quantized 8-bit integer stream (Lesson 3 & Lesson 4)
quantized_stream = numpy.arange(6, dtype=numpy.int8)
# Splitting horizontally into 2 equal parts (Lesson 6)
left_chunk, right_chunk = numpy.hsplit(quantized_stream, 2)
print("Left block:", left_chunk, "Dtype:", left_chunk.dtype) # [0 1 2], int8
print("Right block:", right_chunk, "Dtype:", right_chunk.dtype) # [3 4 5], int88. Динамическая вставка экстремальной аномалии в срез
Задача: Создайте пустой массив из 6 нулей через numpy.zeros (Урок 3). Выделите срез из его первых 3 элементов (Урок 5). Вставьте внутрь этого среза перед элементом с индексом 1 числовое значение 999.0 с помощью numpy.insert() (Урок 6). Выведите полученный в результате вставки массив и прочитайте комментарий в решении о том, изменился ли исходный базовый массив нулей.
Посмотреть решение
python
import numpy
# Allocating zero array placeholder (Lesson 3)
root_zeros = numpy.zeros(6)
# Extracting a slice segment view (Lesson 5)
target_view = root_zeros[:3] # [0. 0. 0.]
# Inserting values creates a completely new array allocation (Lesson 6)
anomalous_array = numpy.insert(target_view, 1, 999.0)
print("New array with insertion:", anomalous_array) # Outputs: [0. 999. 0. 0.]
print("Root zeros array (remains untouched):", root_zeros) # Outputs: [0. 0. 0. 0. 0. 0.]
# Explanation: Because insert() forces a completely new memory allocation on C-level,
# it completely breaks any view links to the parent block.9. Удаление поврежденных NaN элементов из квантованного лога
Задача: Инициализируйте массив вероятностей, содержащий поврежденную точку пропуска: [0.12, numpy.nan, 0.76], явно выставив тип данных numpy.float32 (Урок 4). С помощью функции numpy.delete() удалите элемент с индексом 1 (NaN) (Урок 6). Выведите очищенный массив и посчитайте его точный вес в байтах с помощью атрибута .nbytes (Урок 2).
Посмотреть решение
python
import numpy
# Injected NaN into float32 array allocation (Lesson 4)
corrupted_logs = numpy.array([0.12, numpy.nan, 0.76], dtype=numpy.float32)
# Dropping the damaged item at index 1 (Lesson 6)
sanitized_logs = numpy.delete(corrupted_logs, 1)
print("Sanitized logs array:", sanitized_logs) # Outputs: [0.12 0.76]
print("Physical size in bytes:", sanitized_logs.nbytes) # Outputs: 8 bytes (2 items * 4 bytes)10. Расширение вектора числовой константой с последующим срезом
Задача: Сгенерируйте массив целых чисел от 1 до 3 через numpy.arange(1, 4) (Урок 3). С помощью функции numpy.append() добавьте в его конец математическую константу numpy.e (Урок 7/Урок 6). Выведите итоговый массив на экран и посмотрите в комментарии, к какому типу данных автоматически привел элементы NumPy в силу правил неявного приведения (Урок 7).
Посмотреть решение
python
import numpy
# Generating base integers sequence (Lesson 3)
integer_vector = numpy.arange(1, 4)
# Appending a float scalar constant forces automatic upcasting to float64 (Lesson 6 & Lesson 7)
extended_vector = numpy.append(integer_vector, numpy.e)
print("Extended vector content:", extended_vector)
print("Automatically inferred data type:", extended_vector.dtype) # Outputs: float64