2025, Oct 21 04:16
Матрица N×3N−2 в NumPy: маски для нарастания и спада
Строим матрицу N×3N−2 в Python с NumPy: маски для нарастания и спада, агрегирование весов и вывод в Pandas DataFrame. Применимо к задачам перестрахования.
Когда последовательность весов A1, A2, ..., AN надо объединить в прямоугольную структуру, которая нарастает, достигает пика, а затем снижается, проходя по кругу по весам, логику легко усложнить. Аккуратный способ получить такую форму — использовать маски NumPy, которые заполняют диагонали по предсказуемому шаблону, а затем агрегировать их с заданными весами. При необходимости результат можно превратить в Pandas DataFrame.
Что нужно получить
Для N чисел A1..AN строим массив с N строками и 3N−2 столбцами. Каждая строка соответствует сдвинутой точке старта, а столбцы отражают накопление по последовательности: сначала сумма растет, добавляя следующие значения, затем уменьшается, отбрасывая самые ранние, при этом последовательность циклически проходит по A. Например, при N = 3 это дает:
    0   1   2   3   4   5   6
0   1.0 3.0 6.0 5.0 3.0 0.0 0.0
1   0.0 2.0 5.0 6.0 4.0 1.0 0.0
2   0.0 0.0 3.0 4.0 6.0 3.0 2.0Такой шаблон, например, полезен в перестраховании с рисками, присоединяющимися в течение года: A1..AN могут обозначать месячные доли заключенного бизнеса, а каждая строка показывает, как суммы записанного объема накапливаются, а затем уменьшаются по мере окончания действия договоров.
Пример кода: базовый шаблон
Суть в том, чтобы для каждого сдвига построить маску из единиц, которая отмечает, где участвует соответствующий вес, а затем перемножить и просуммировать маски. Для заданного сдвига маска состоит из двух прямоугольных блоков из единиц: первый «растет» вниз до текущего сдвига, второй продолжается по оставшимся строкам. Ниже — минимальная конструкция этих масок:
import numpy as np
dim = 3
for ofs in range(dim):  # ofs обозначает текущий сдвиг
    mask = np.zeros((dim, 3*dim - 2))
    left = dim + ofs
    right = 2*dim + ofs
    mask[:ofs+1, ofs:left] = 1
    mask[ofs+1:, left:right] = 1При dim = 3 маски выглядят так:
[[1. 1. 1. 0. 0. 0. 0.]
 [0. 0. 0. 1. 1. 1. 0.]
 [0. 0. 0. 1. 1. 1. 0.]]
[[0. 1. 1. 1. 0. 0. 0.]
 [0. 1. 1. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1. 1. 1.]]
[[0. 0. 1. 1. 1. 0. 0.]
 [0. 0. 1. 1. 1. 0. 0.]
 [0. 0. 1. 1. 1. 0. 0.]]Почему это работает
Каждая маска задает «диагональное» окно вкладов. Первый блок единиц начинается в столбце, равном сдвигу, и заполняется вниз до текущей строки — так формируется нарастание. Второй блок включается от столбца dim + сдвиг и проходит по оставшимся строкам — так получается спад. Суммируя эти маски, каждая из которых умножена на свой вес, мы получаем матрицу, где каждый столбец соответствует определенной комбинации последовательных весов, а в центральной зоне видны максимальные суммы N подряд идущих членов. Размер — N на 3N−2, что соответствует участку «нарастание — пик — спад».
Полное решение для любого N
Вот полный алгоритм: каждую маску умножаем на ее вес и агрегируем результат. Сначала формируется массив NumPy, затем он преобразуется в Pandas DataFrame.
import numpy as np
import pandas as pd
dim = 3
weights = [1, 2, 3]
accum = np.zeros((dim, 3*dim - 2))
for ofs in range(dim):
    mask = np.zeros((dim, 3*dim - 2))
    left = dim + ofs
    right = 2*dim + ofs
    mask[:ofs+1, ofs:left] = 1
    mask[ofs+1:, left:right] = 1
    accum += weights[ofs] * mask
pd.DataFrame(accum)При dim = 3 и weights [1, 2, 3] получаем такой DataFrame:
    0   1   2   3   4   5   6
0   1.0 3.0 6.0 5.0 3.0 0.0 0.0
1   0.0 2.0 5.0 6.0 4.0 1.0 0.0
2   0.0 0.0 3.0 4.0 6.0 3.0 2.0Почему это стоит освоить
Эта конструкция прямо повторяет поведение таких графиков на практике, например при присоединении рисков в течение года в перестраховании. Метод опирается на однородную числовую природу и простую индексацию в NumPy — здесь это как нельзя кстати. Если удобнее мыслить диагоналями, в вашем распоряжении и инструменты NumPy вроде diag для сборки диагональных структур. Подход устойчивее, чем попытки менять столбцы, многократно добавляя и удаляя слагаемые, и без лишних усилий обобщается на любое N.
Итоги
Моделируйте нарастание и спад с помощью масок и агрегируйте их с заданными весами. Работайте в NumPy, чтобы построить массив N на 3N−2, а для представления переведите его в DataFrame. Смотрите на задачу как на сдвинутые окна, превращающиеся в диагонали, — пусть маски ведут «бухгалтерию». Так логика остается прозрачной, а масштабирование на разные N не требует частных исключений.