Skip to content

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 сортировка числовых векторов может быть выполнена двумя способами, которые принципиально по-разному работают с оперативной памятью:

  1. Функция модуля numpy.sort(arr): Принимает массив, создает его копию в памяти, сортирует её по возрастанию на C-скорости и возвращает новый отсортированный массив. Исходные данные не меняются.
  2. Метод объекта 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], int8

8. Динамическая вставка экстремальной аномалии в срез ​

Задача: Создайте пустой массив из 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