14  I. SPiCT: МОДЕЛЬ

14.1 Введение в SPiCT: от данных к управлению запасами

14.2 Основы моделирования и диагностики в SPiCT

Если смотреть на оценку запасов «с высоты орбиты», всё кажется почти прямолинейным: есть выловы, есть индексы биомассы, есть модель — значит, будет и рекомендация. Но на палубе реальности мозг слишком любит простые истории: «подставим числа — получим MSY». SPiCT как раз дисциплинирует эту склонность: заставляет назвать источники неопределённости своими именами, зафиксировать момент наблюдения индексов, выбрать априорные распределения там, где данных мало, и пройти полноценную диагностику. Это не магический чёрный ящик, а аккуратный перевод временных рядов выловов и индексов в управленческие ориентиры — с явной лентой интервалов.

Что такое SPiCT по сути. Это стохастическая продукционная модель в непрерывном времени, где динамика биомассы отделена от наблюдательной ошибки, а промысловая смертность и продукция описываются гладко и физически правдоподобно. Минимальный набор данных — годовые выловы и хотя бы один индекс относительной биомассы (CPUE, научная съёмка), привязанные ко времени года (середина, конец и т. п.). Выход — оценка r и K, ориентиры MSY (Bmsy, Fmsy, MSY), траектории B(t) и F(t), их отношения к Bmsy и Fmsy, а также диагностика, без которой лучше не приближаться к выработке рекомендаций по вылову.

Где нас поджидают ловушки. - Масштаб и уловистость (q): абсолютные величины K и B зависят от шкалы индекса; безопаснее мыслить в B/Bmsy и F/Fmsy. «Абсолютная биомасса» без внешней калибровки — иллюзия точности. - Время наблюдения: индекс «за июль» и вылов «за год» — не одно и то же. Ошибка хронометража превращается в систематическое смещение. - Неидентифицируемость r и K: короткий ряд или один слабый индекс «перетягивают» правдоподобие; априоры помогают, но создают ответственность за допущения. - Дрейф уловистости и структура флота: технологический прогресс маскируется под «рост запаса», если его не вынести на свет. - Последние годы: предварительные данные искажают тренды; в SPiCT для них есть честный инструмент — повышение дисперсии (stdevfac). - Диагностика не формальность: OSA‑остатки, Ljung–Box, ретроанализ (Mohn’s rho) и профили правдоподобия — фильтр от «правдоподобных, но хрупких» решений.

Как читать результат. SPiCT даёт распределения, а не единственные точечные числа. Ключ — в относительных показателях и интервалах: B/Bmsy и F/Fmsy с 95% ДИ, MSY с неопределённостью, вероятности пребывания в «зелёной зоне». Совет по вылову — не прямой продукт «умножить Fmsy на B», а часть цепочки: оценка → правило пегулирования промысла (HCR) → проверка устойчивости (желательно — через MSE). Хорошая история — та, в которой видно, чему мы обязаны данным, а чему — допущениям.

Что делаем в этой главе. - Формируем вход: выловы, индексы, их время наблюдения; настраиваем априорные распределения (n, K, начальная доля bkfrac), дисперсии и численный шаг. - Оцениваем модель и проверяем: сходимость, OSA‑остатки, автокорреляция, ретро и Mohn’s rho, чувствительность к априорным распределениям. - Извлекаем ориентиры: MSY, Bmsy, Fmsy; интерпретируем текущее состояние (B/Bmsy, F/Fmsy). - Готовим мост к управлению: показываем, как из оценок рождаются кандидаты правил (HCR) и почему без явной неопределённости лучше не говорить про ОДУ

Границы метода. SPiCT не заменяет прямые съёмки и не исправляет дефекты данных «по дороге». Он делает видимой структуру неопределённости, где раньше была уверенность «на глаз». Именно за это его стоит любить: меньше иллюзий контроля — больше проверяемых решений.

Полный скрипт можно скачать по ссылке. Ниже приводится исполнение скрипта.

# ===============================================================
#     СКРИПТ 1: ОСНОВЫ МОДЕЛИРОВАНИЯ И ДИАГНОСТИКИ В SPiCT
#     Курс: Оценка водных биоресурсов при недостатке данных в R
#     Автор: Баканев С. В.
#     Дата создания: 28.08.2025
# ===============================================================

# ======================= ВВЕДЕНИЕ =============================
# SPiCT (Surplus Production model in Continuous Time) - это
# стохастическая продукционная модель для оценки запасов рыбы
# при ограниченных данных. Модель требует только временные ряды
# уловов и индексов биомассы (например, CPUE или научные съемки)

# ------------------- 1. ПОДГОТОВКА СРЕДЫ --------------------

## 1.1 Очистка рабочей среды (удаляем все объекты)
rm(list = ls())

## 1.2 Загрузка необходимых библиотек
library(spict)      # Основной пакет для SPiCT моделирования
Загрузка требуемого пакета: TMB
Welcome to spict_v1.3.8@107a32
library(tidyverse)  # Для обработки данных и визуализации
-- Attaching core tidyverse packages ------------------------ tidyverse 2.0.0 --
v dplyr     1.1.4     v readr     2.1.5
v forcats   1.0.0     v stringr   1.5.1
v ggplot2   3.5.2     v tibble    3.2.1
v lubridate 1.9.4     v tidyr     1.3.1
v purrr     1.0.4     
-- Conflicts ------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
i Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(ggplot2)    # Дополнительные возможности построения графиков

## 1.3 Установка рабочей директории
# ВАЖНО: Измените путь на вашу рабочую папку
setwd("C:/SPICT") 

## 1.4 Настройка вывода чисел (опционально)
options(scipen = 999)  # Отключение научной нотации
options(digits = 4)    # Количество значащих цифр

# ------------------- 2. ЗАГРУЗКА ДАННЫХ --------------------

cat("\n========== ЗАГРУЗКА ДАННЫХ ==========\n")

========== ЗАГРУЗКА ДАННЫХ ==========
## 2.1 Временной ряд (годы наблюдений)
# Период наблюдений с 2005 по 2024 год
Year <- 2005:2024

## 2.2 Данные по вылову (в тысячах тонн)
# Представляют общий коммерческий вылов по годам
# Обратите внимание на тренд: рост до 2014 г., затем снижение
Catch <- c(5,  7,  6, 10, 14, 25, 28, 30, 32, 35,    # 2005-2014
          25, 20, 15, 12, 10, 12, 10, 13, 11, 12)    # 2015-2024

## 2.3 Индекс CPUE (улов на единицу усилия)
# Промысловый индекс, отражающий относительную биомассу
# Собирается в середине года (июль) во время промысла
CPUEIndex <- c(27.427120, 26.775958, 16.811997, 22.979653, 29.048568, 
               29.996072, 16.476301, 17.174455, 10.537272, 14.590435,
                8.286352, 11.394168, 15.537878, 13.791166, 11.527548, 
               15.336093, 12.154069, 15.568450, 16.221933, 13.421132)

## 2.4 Индекс BESS (биомасса по научной съемке)
# Независимая оценка биомассы из научных траловых съемок
# Проводится в 4-м квартале года (октябрь)
# NA в первый год означает отсутствие съемки
BESSIndex <- c(       NA, 16.270375, 20.691355, 15.141784, 18.594620, 
               15.975548, 13.792012, 13.328805, 11.659744, 11.753855,
                9.309859,  7.104886,  7.963839,  9.161322, 10.271221, 
                9.822960, 10.347376, 11.703610, 13.679876, 13.413696)

## 2.5 Визуализация исходных данных
par(mfrow = c(2, 2), mar = c(4, 4, 2, 1))

# График вылова
plot(Year, Catch, type = "b", pch = 19, col = "red",
     main = "Динамика вылова", xlab = "Год", ylab = "Вылов (тыс. т)")
grid()

# График CPUE
plot(Year, CPUEIndex, type = "b", pch = 19, col = "blue",
     main = "Индекс CPUE", xlab = "Год", ylab = "CPUE")
grid()

# График BESS
plot(Year, BESSIndex, type = "b", pch = 19, col = "darkgreen",
     main = "Индекс BESS", xlab = "Год", ylab = "BESS")
grid()

# График всех индексов (нормализованных)
plot(Year, CPUEIndex/mean(CPUEIndex, na.rm = TRUE), type = "l", 
     col = "blue", lwd = 2, ylim = c(0, 2),
     main = "Сравнение индексов", xlab = "Год", ylab = "Отн. индекс")
lines(Year, BESSIndex/mean(BESSIndex, na.rm = TRUE), col = "darkgreen", lwd = 2)
legend("topright", c("CPUE", "BESS"), col = c("blue", "darkgreen"), lty = 1, lwd = 2)
grid()

par(mfrow = c(1, 1))

# ------------------- 3. ФОРМАТИРОВАНИЕ ДАННЫХ ДЛЯ SPiCT --------------------

cat("\n========== ПОДГОТОВКА ДАННЫХ ДЛЯ МОДЕЛИ ==========\n")

========== ПОДГОТОВКА ДАННЫХ ДЛЯ МОДЕЛИ ==========
## 3.1 Создание входного объекта для SPiCT
# SPiCT требует специальный формат данных в виде списка
input_data <- list(
  
  # Временной ряд вылова (обычно конец года)
  timeC = Year,        
  obsC = Catch,        
  
  # Временные ряды индексов
  # ВАЖНО: время индексов должно отражать когда они собраны
  timeI = list(
    Year + 0.5,      # CPUE собирается в середине года (июль)
    Year + 0.75      # BESS проводится в 4-м квартале (октябрь)
  ),
  
  # Значения индексов (в том же порядке, что и timeI)
  obsI = list(
    CPUEIndex,       
    BESSIndex        
  )
)

## 3.2 Проверка и валидация входных данных
# Функция check.inp выполняет базовую проверку данных
# и удаляет нулевые, отрицательные и NA значения
inp <- check.inp(input_data, verbose = TRUE)
Removing zero, negative, and NAs in  I  series  2  
# Вывод структуры данных
cat("\nСтруктура входных данных:\n")

Структура входных данных:
cat("Количество наблюдений вылова:", length(inp$obsC), "\n")
Количество наблюдений вылова: 20 
cat("Количество индексов:", length(inp$obsI), "\n")
Количество индексов: 2 
cat("Годы наблюдений:", range(inp$timeC), "\n")
Годы наблюдений: 2005 2024 
# ------------------- 4. НАСТРОЙКА МОДЕЛИ --------------------

cat("\n========== НАСТРОЙКА ПАРАМЕТРОВ МОДЕЛИ ==========\n")

========== НАСТРОЙКА ПАРАМЕТРОВ МОДЕЛИ ==========
## 4.1 Установка априорных распределений (priors)

### 4.1.1 Параметр формы продукционной кривой (n)
# n = 2 соответствует модели Шефера (симметричная кривая)
# Фиксируем n = 2, так как данных недостаточно для его оценки
inp$priors$logn <- c(log(2), 0.1, 1)  # (среднее, SD, использовать?)
inp$ini$logn <- log(2)                 # Начальное значение
inp$phases$logn <- -1                  # -1 означает не оценивать

### 4.1.2 Априор для несущей способности (K)
# K - максимальная биомасса, которую может поддерживать среда
# Используем информативный априор на основе экспертных оценок
inp$priors$logK <- c(log(150), 0.7, 1)  # log(150) тыс. тонн, CV ≈ 70%

### 4.1.3 Априор для начального состояния запаса
# logbkfrac = log(B_начальное/K)
# 0.75 означает, что в начале временного ряда запас был на 75% от K
inp$priors$logbkfrac <- c(log(0.75), 0.25, 1)

### 4.1.4 Априоры для параметров роста (опционально)
# Если есть информация о темпе роста популяции
# inp$priors$logr <- c(log(0.3), 0.5, 1)

## 4.2 Настройка неопределенности данных

### 4.2.1 Увеличение неопределенности для последних наблюдений
# Последние данные часто предварительные и менее надежные
inp$stdevfacC[length(inp$stdevfacC)] <- 2      # Удваиваем SD для последнего вылова
inp$stdevfacI[[2]][length(inp$stdevfacI[[2]])] <- 2  # Для последнего BESS

### 4.2.2 Установка минимальной неопределенности (опционально)
# inp$stdevfacC <- pmax(inp$stdevfacC, 0.2)  # Минимум 20% CV

## 4.3 Технические настройки модели

### 4.3.1 Временной шаг для численного интегрирования
# Меньшие значения = выше точность, но медленнее расчет
inp$dteuler <- 1/16  # 16 шагов в году

### 4.3.2 Включение расчета матрицы ковариации
# Необходимо для оценки неопределенности и диагностики
inp$getJointPrecision <- TRUE

### 4.3.3 Робастность оценок (опционально)
# inp$robflag <- TRUE  # Устойчивость к выбросам

# ------------------- 5. ЗАПУСК МОДЕЛИ --------------------

cat("\n========== ОЦЕНКА ПАРАМЕТРОВ МОДЕЛИ ==========\n")

========== ОЦЕНКА ПАРАМЕТРОВ МОДЕЛИ ==========
## 5.1 Настройка оптимизатора
# Увеличиваем лимиты итераций для сложных случаев
inp$optimiser.control = list(
  iter.max = 1e5,    # Максимум итераций
  eval.max = 1e5,    # Максимум вычислений функции
  rel.tol = 1e-10    # Относительная точность
)

## 5.2 Подгонка модели
# Основная функция для оценки параметров
cat("Запуск оптимизации...\n")
Запуск оптимизации...
fit <- fit.spict(inp)

## 5.3 Проверка сходимости
if (fit$opt$convergence == 0) {
  cat("✓ Модель успешно сошлась\n")
} else {
  cat("⚠ Проблемы со сходимостью. Код:", fit$opt$convergence, "\n")
  cat("Сообщение:", fit$opt$message, "\n")
}
<U+2713> Модель успешно сошлась
## 5.4 Добавление остатков OSA для диагностики
# OSA (One-Step-Ahead) остатки используются для проверки модели
fit <- calc.osa.resid(fit)

## 5.5 Вывод основных результатов
print(summary(fit))
Convergence: 0  MSG: relative convergence (4)
Objective function at optimum: -4.835549
Euler time step (years):  1/16 or 0.0625
Nobs C: 20,  Nobs I1: 20,  Nobs I2: 19

Residual diagnostics (p-values)
    shapiro   bias    acf   LBox shapiro bias acf LBox  
 C   0.1269 0.4387 0.0562 0.1348       -    -   .    -  
 I1  0.9555 0.7813 0.3360 0.6800       -    -   -    -  
 I2  0.9825 0.9472 0.1390 0.3601       -    -   -    -  

Priors
      logn  ~  dnorm[log(2), 0.1^2]
  logalpha  ~  dnorm[log(1), 2^2]
   logbeta  ~  dnorm[log(1), 2^2]
      logK  ~  dnorm[log(150), 0.7^2]
 logbkfrac  ~  dnorm[log(0.75), 0.25^2]

Fixed parameters
   fixed.value  
 n           2  

Model parameter estimates w 95% CI 
         estimate      cilow     ciupp log.est  
 alpha1  11.94775   2.680587  53.25274  2.4805  
 alpha2   5.06977   1.127092  22.80433  1.6233  
 beta     0.18792   0.037152   0.95057 -1.6717  
 r        0.37684   0.289449   0.49063 -0.9759  
 rc       0.37684   0.289449   0.49063 -0.9759  
 rold     0.37684   0.289449   0.49063 -0.9759  
 m       17.85915  16.267157  19.60694  2.8825  
 K      189.56564 153.817727 233.62154  5.2447  
 q1       0.14458   0.111028   0.18827 -1.9339  
 q2       0.11048   0.086140   0.14168 -2.2030  
 sdb      0.01792   0.004082   0.07867 -4.0219  
 sdf      0.32055   0.218175   0.47097 -1.1377  
 sdi1     0.21410   0.156324   0.29322 -1.5413  
 sdi2     0.09085   0.064838   0.12729 -2.3986  
 sdc      0.06024   0.014357   0.25275 -2.8094  
 
Deterministic reference points (Drp)
       estimate   cilow    ciupp log.est  
 Bmsyd  94.7828 76.9089 116.8108   4.552  
 Fmsyd   0.1884  0.1447   0.2453  -1.669  
 MSYd   17.8591 16.2672  19.6069   2.883  
Stochastic reference points (Srp)
       estimate   cilow    ciupp log.est rel.diff.Drp  
 Bmsys  94.7336 76.8594 116.7646   4.551   -0.0005195  
 Fmsys   0.1883  0.1447   0.2452  -1.669   -0.0004215  
 MSYs   17.8423 16.2533  19.5868   2.882   -0.0009416  

States w 95% CI (inp$msytype: s)
                estimate    cilow    ciupp log.est  
 B_2024.94      118.8550 96.94825 145.7118  4.7779  
 F_2024.94        0.1032  0.06467   0.1646 -2.2713  
 B_2024.94/Bmsy   1.2546  1.08443   1.4515  0.2268  
 F_2024.94/Fmsy   0.5478  0.34183   0.8779 -0.6018  

Predictions w 95% CI (inp$msytype: s)
                prediction     cilow    ciupp log.est  
 B_2026.00        123.1089 100.33664 151.0495  4.8131  
 F_2026.00          0.1032   0.04643   0.2293 -2.2713  
 B_2026.00/Bmsy     1.2995   1.11491   1.5147  0.2620  
 F_2026.00/Fmsy     0.5478   0.24587   1.2206 -0.6018  
 Catch_2025.00     12.4910   7.30335  21.3633  2.5250  
 E(B_inf)         137.4812        NA       NA  4.9235  
NULL
# ------------------- 6. ДИАГНОСТИКА МОДЕЛИ --------------------

cat("\n========== ДИАГНОСТИКА МОДЕЛИ ==========\n")

========== ДИАГНОСТИКА МОДЕЛИ ==========
## 6.1 Проверка остатков

### 6.1.1 Тест Шапиро-Уилка на нормальность
cat("\n--- Тест на нормальность остатков (Shapiro-Wilk) ---\n")

--- Тест на нормальность остатков (Shapiro-Wilk) ---
cat(sprintf("Уловы (C): p-value = %.4f %s\n", 
            fit$diagn$shapiroC.p, 
            ifelse(fit$diagn$shapiroC.p > 0.05, "✓", "⚠")))
Уловы (C): p-value = 0.1269 <U+2713>
cat(sprintf("Индекс 1 (I1): p-value = %.4f %s\n", 
            fit$diagn$shapiroI1.p, 
            ifelse(fit$diagn$shapiroI1.p > 0.05, "✓", "⚠")))
Индекс 1 (I1): p-value = 0.9555 <U+2713>
cat(sprintf("Индекс 2 (I2): p-value = %.4f %s\n", 
            fit$diagn$shapiroI2.p, 
            ifelse(fit$diagn$shapiroI2.p > 0.05, "✓", "⚠")))
Индекс 2 (I2): p-value = 0.9825 <U+2713>
### 6.1.2 Проверка автокорреляции (Ljung-Box тест)
cat("\n--- Проверка автокорреляции (Ljung-Box тест) ---\n")

--- Проверка автокорреляции (Ljung-Box тест) ---
cat(sprintf("Уловы (C): p-value = %.4f %s\n", 
            fit$diagn$LBoxC.p, 
            ifelse(fit$diagn$LBoxC.p > 0.05, "✓", "⚠")))
Уловы (C): p-value = 0.1348 <U+2713>
cat(sprintf("Индекс 1 (I1): p-value = %.4f %s\n", 
            fit$diagn$LBoxI1.p, 
            ifelse(fit$diagn$LBoxI1.p > 0.05, "✓", "⚠")))
Индекс 1 (I1): p-value = 0.6800 <U+2713>
cat(sprintf("Индекс 2 (I2): p-value = %.4f %s\n", 
            fit$diagn$LBoxI2.p, 
            ifelse(fit$diagn$LBoxI2.p > 0.05, "✓", "⚠")))
Индекс 2 (I2): p-value = 0.3601 <U+2713>
### 6.1.3 Дополнительные диагностические тесты
cat("\n--- Дополнительная диагностика ---\n")

--- Дополнительная диагностика ---
cat(sprintf("Смещение уловов (biasC): p-value = %.4f %s\n", 
            fit$diagn$biasC.p, 
            ifelse(fit$diagn$biasC.p > 0.05, "✓", "⚠")))
Смещение уловов (biasC): p-value = 0.4387 <U+2713>
cat(sprintf("Автокорреляция уловов (acfC): p-value = %.4f %s\n", 
            fit$diagn$acfC.p, 
            ifelse(fit$diagn$acfC.p > 0.05, "✓", "⚠")))
Автокорреляция уловов (acfC): p-value = 0.0562 <U+2713>
## 6.2 Визуальная диагностика
# Создаем диагностические графики
plotspict.diagnostic(fit)

## 6.3 Ретроспективный анализ
# Проверяет устойчивость оценок при удалении последних лет
cat("\n--- Ретроспективный анализ ---\n")

--- Ретроспективный анализ ---
ret <- retro(fit, nretroyear = 5)
plotspict.retro(ret)

     FFmsy      BBmsy 
 0.0029188 -0.0002271 
# Расчет ретро-смещения (Mohn's rho)
rho <- mohns_rho(ret)
cat("Mohn's rho для биомассы:", round(rho["BBmsy"], 4), "\n")
Mohn's rho для биомассы: -0.0002 
cat("Mohn's rho для F:", round(rho["FFmsy"], 4), "\n")
Mohn's rho для F: 0.0029 
cat("Приемлемые значения: |rho| < 0.2\n")
Приемлемые значения: |rho| < 0.2
## 6.4 Анализ чувствительности к априорным распределениям
cat("\n--- Анализ чувствительности ---\n")

--- Анализ чувствительности ---
# Тест без априоров
inp_no_prior <- inp
inp_no_prior$priors <- list()
fit_no_prior <- fit.spict(inp_no_prior)

# Сравнение оценок
cat("Изменение оценок без априоров:\n")
Изменение оценок без априоров:
cat("K:", round((exp(fit_no_prior$par.fixed["logK"]) - 
               exp(fit$par.fixed["logK"])) / 
               exp(fit$par.fixed["logK"]) * 100, 1), "%\n")
K: 0.4 %
## 6.5 Сравнение априорных и апостериорных распределений (Проверка профилей правдоподобия) 
# Помогает оценить идентифицируемость параметров
par(mfrow = c(2, 2))
plotspict.priors(fit)

par(mfrow = c(1, 1))
# ------------------- 7. ИНТЕРПРЕТАЦИЯ РЕЗУЛЬТАТОВ --------------------

cat("\n========== ИНТЕРПРЕТАЦИЯ РЕЗУЛЬТАТОВ ==========\n")

========== ИНТЕРПРЕТАЦИЯ РЕЗУЛЬТАТОВ ==========
## 7.1 Извлечение ключевых параметров
get_estimate <- function(fit, param) {
  val <- get.par(param, fit, exp = TRUE)
  return(c(estimate = val[1], lower = val[2], upper = val[3]))
}

## 7.2 Параметры модели
cat("\n--- Оценки параметров модели ---\n")

--- Оценки параметров модели ---
r_est <- get_estimate(fit, "logr")
cat(sprintf("r (темп роста): %.3f [%.3f - %.3f]\n", 
            r_est[2], r_est[1], r_est[3]))
r (темп роста): 0.377 [0.289 - 0.491]
K_est <- get_estimate(fit, "logK")
cat(sprintf("K (несущая способность): %.1f [%.1f - %.1f] тыс. т\n", 
            K_est[2], K_est[1], K_est[3]))
K (несущая способность): 189.6 [153.8 - 233.6] тыс. т
## 7.3 Ориентиры управления (Референсные точки)
cat("\n--- Ориентиры управления (MSY) ---\n")

--- Ориентиры управления (MSY) ---
MSY <- get_estimate(fit, "logMSY")
cat(sprintf("MSY: %.1f [%.1f - %.1f] тыс. т/год\n", 
            MSY[2], MSY[1], MSY[3]))
MSY: 17.8 [16.3 - 19.6] тыс. т/год
Bmsy <- get_estimate(fit, "logBmsy")
cat(sprintf("Bmsy: %.1f [%.1f - %.1f] тыс. т\n", 
            Bmsy[2], Bmsy[1], Bmsy[3]))
Bmsy: 94.7 [76.9 - 116.8] тыс. т
Fmsy <- get_estimate(fit, "logFmsy")
cat(sprintf("Fmsy: %.3f [%.3f - %.3f] год⁻¹\n", 
            Fmsy[2], Fmsy[1], Fmsy[3]))
Fmsy: 0.188 [0.145 - 0.245] год<U+207B><U+00B9>
## 7.4 Текущее состояние запаса
cat("\n--- Текущее состояние запаса (последний год) ---\n")

--- Текущее состояние запаса (последний год) ---
current_year <- max(inp$timeC)

B_current <- get_estimate(fit, "logB")
cat(sprintf("Биомасса: %.1f [%.1f - %.1f] тыс. т\n", 
            B_current[2], B_current[1], B_current[3]))
Биомасса: 103.8 [103.0 - 104.6] тыс. т
F_current <- get_estimate(fit, "logF")
cat(sprintf("F: %.3f [%.3f - %.3f] год⁻¹\n", 
            F_current[2], F_current[1], F_current[3]))
F: 0.019 [0.019 - 0.019] год<U+207B><U+00B9>
# Относительные показатели
B_Bmsy <- get_estimate(fit, "logBBmsy")
cat(sprintf("B/Bmsy: %.2f [%.2f - %.2f]\n", 
            B_Bmsy[2], B_Bmsy[1], B_Bmsy[3]))
B/Bmsy: 1.17 [1.16 - 1.18]
F_Fmsy <- get_estimate(fit, "logFFmsy")
cat(sprintf("F/Fmsy: %.2f [%.2f - %.2f]\n", 
            F_Fmsy[2], F_Fmsy[1], F_Fmsy[3]))
F/Fmsy: 0.10 [0.10 - 0.11]
# Интерпретация состояния
if (B_Bmsy[1] > 1 && F_Fmsy[1] < 1) {
  cat("\n✓ Запас в хорошем состоянии (зеленая зона)\n")
} else if (B_Bmsy[1] < 0.5) {
  cat("\n⚠ Запас истощен (красная зона)\n")
} else if (F_Fmsy[1] > 1) {
  cat("\n⚠ Происходит перелов (желтая зона)\n")
} else {
  cat("\n⚠ Запас в переходном состоянии\n")
}

<U+2713> Запас в хорошем состоянии (зеленая зона)
# ------------------- 8. ГРАФИЧЕСКАЯ ВИЗУАЛИЗАЦИЯ --------------------

#cat("\n========== СОЗДАНИЕ ГРАФИКОВ ==========\n")

## 8.1 Стандартные графики SPiCT
#pdf("SPiCT_results.pdf", width = 12, height = 10)

# График 1: Сводка результатов
plot(fit)

# График 2: Временные ряды
plotspict.biomass(fit)

plotspict.f(fit)

plotspict.catch(fit)

# График 3: Фазовая диаграмма Кобе
plotspict.fb(fit)

# График 4: Продукционная кривая
plotspict.production(fit)

#dev.off()
#cat("Графики сохранены в файл 'SPiCT_results.pdf'\n")

## 8.2 Альтернативный подход - детальные графики

# Функция для извлечения временных рядов из SPiCT
extract_time_series <- function(fit, param_name) {
  # Получаем оценки параметра
  param_values <- get.par(param_name, fit, exp = TRUE)
  
  # Временные точки
  time_points <- fit$inp$time
  
  # Создаем dataframe
  df <- data.frame(
    time = time_points,
    estimate = param_values[, "est"],
    lower = param_values[, "ll"],
    upper = param_values[, "ul"]
  )
  
  return(df)
}

# Извлекаем все нужные временные ряды
df_B <- extract_time_series(fit, "logB")
df_F <- extract_time_series(fit, "logF")
df_BBmsy <- extract_time_series(fit, "logBBmsy")
df_FFmsy <- extract_time_series(fit, "logFFmsy")

# Создаем панель графиков
library(gridExtra)

Присоединяю пакет: 'gridExtra'
Следующий объект скрыт от 'package:dplyr':

    combine
# График 1: Биомасса
g1 <- ggplot(df_B, aes(x = time)) +
  geom_ribbon(aes(ymin = lower, ymax = upper), alpha = 0.3, fill = "blue") +
  geom_line(aes(y = estimate), color = "darkblue", size = 1.2) +
  geom_hline(yintercept = Bmsy[1], linetype = "dashed", color = "red") +
  labs(title = "A. Биомасса", x = "Год", y = "Биомасса (тыс. т)") +
  theme_minimal()
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
i Please use `linewidth` instead.
# График 2: Промысловая смертность
g2 <- ggplot(df_F, aes(x = time)) +
  geom_ribbon(aes(ymin = lower, ymax = upper), alpha = 0.3, fill = "orange") +
  geom_line(aes(y = estimate), color = "darkorange", size = 1.2) +
  geom_hline(yintercept = Fmsy[1], linetype = "dashed", color = "red") +
  labs(title = "B. Промысловая смертность", x = "Год", y = "F (год⁻¹)") +
  theme_minimal()

# График 3: B/Bmsy
g3 <- ggplot(df_BBmsy, aes(x = time)) +
  geom_ribbon(aes(ymin = lower, ymax = upper), alpha = 0.3, fill = "green") +
  geom_line(aes(y = estimate), color = "darkgreen", size = 1.2) +
  geom_hline(yintercept = 1, linetype = "dashed", color = "red") +
  geom_hline(yintercept = 0.5, linetype = "dotted", color = "orange") +
  labs(title = "C. Относительная биомасса", x = "Год", y = "B/Bmsy") +
  theme_minimal()

# График 4: F/Fmsy
g4 <- ggplot(df_FFmsy, aes(x = time)) +
  geom_ribbon(aes(ymin = lower, ymax = upper), alpha = 0.3, fill = "purple") +
  geom_line(aes(y = estimate), color = "darkviolet", size = 1.2) +
  geom_hline(yintercept = 1, linetype = "dashed", color = "red") +
  labs(title = "D. Относительная смертность", x = "Год", y = "F/Fmsy") +
  theme_minimal()

# Объединяем графики
grid_plot <- grid.arrange(g1, g2, g3, g4, ncol = 2,
                         top = "Временные ряды ключевых параметров")

# Сохраняем
ggsave("SPiCT_time_series.png", grid_plot, width = 12, height = 10, dpi = 300)
cat("График временных рядов сохранен как 'SPiCT_time_series.png'\n")
График временных рядов сохранен как 'SPiCT_time_series.png'
# ------------------- 9. СОХРАНЕНИЕ РЕЗУЛЬТАТОВ --------------------

cat("\n========== СОХРАНЕНИЕ РЕЗУЛЬТАТОВ ==========\n")

========== СОХРАНЕНИЕ РЕЗУЛЬТАТОВ ==========
## 9.1 Сохранение объекта модели
#saveRDS(fit, "spict_model_fit.rds")
#cat("Модель сохранена в 'spict_model_fit.rds'\n")

## 9.2 Экспорт таблицы с результатами
results_table <- data.frame(
  Parameter = c("r", "K", "MSY", "Bmsy", "Fmsy", 
                "B_current", "F_current", "B/Bmsy", "F/Fmsy"),
  Estimate = c(r_est[1], K_est[1], MSY[1], Bmsy[1], Fmsy[1],
               B_current[1], F_current[1], B_Bmsy[1], F_Fmsy[1]),
  Lower_CI = c(r_est[2], K_est[2], MSY[2], Bmsy[2], Fmsy[2],
               B_current[2], F_current[2], B_Bmsy[2], F_Fmsy[2]),
  Upper_CI = c(r_est[3], K_est[3], MSY[3], Bmsy[3], Fmsy[3],
               B_current[3], F_current[3], B_Bmsy[3], F_Fmsy[3])
)

write.csv(results_table, "spict_results.csv", row.names = FALSE)
cat("Таблица результатов сохранена в 'spict_results.csv'\n")
Таблица результатов сохранена в 'spict_results.csv'
## 9.3 Создание отчета
#cat("\n========== ИТОГОВЫЙ ОТЧЕТ ==========\n")
#cat("Дата анализа:", format(Sys.Date(), "%d.%m.%Y"), "\n")
#cat("Версия SPiCT:", packageVersion("spict"), "\n")
#cat("Период данных:", min(inp$timeC), "-", max(inp$timeC), "\n")
#cat("Количество наблюдений:", length(inp$obsC), "\n")
#cat("Сходимость модели:", ifelse(fit$opt$convergence == 0, "Да", "Нет"), "\n")
#cat("\nОСНОВНЫЕ ВЫВОДЫ:\n")
#cat("1. Текущая биомасса составляет", round(B_Bmsy[1]*100), "% от Bmsy\n")
#cat("2. Промысловая смертность составляет", round(F_Fmsy[1]*100), "% от Fmsy\n")
#cat("3. Рекомендуемый вылов (при F=Fmsy):", round(Fmsy[1] * B_current[1], 1), "тыс. т\n")

#cat("\n=============== КОНЕЦ АНАЛИЗА ===============\n")

14.3 Результаты выполнения первого скрипта

Анализ выполнен для условного запаса с данными за 2005–2024 годы. В качестве индексов биомассы использовались промысловый индекс CPUE (собираемый в середине года) и индекс научной съемки BESS (четвертый квартал). Визуализация исходных данных показала ожидаемую динамику: рост вылова до 2014 года с последующим снижением, что согласуется с снижающимися трендами в индексах биомассы.

Модель успешно сошлась (код сходимости 0), что уже можно считать небольшим достижением — статистические модели не всегда сходятся с первого раза, особенно при работе с реальными, а не идеализированными данными. Диагностика остатков не выявила серьезных проблем: тесты на нормальность (Шапиро-Уилк) и автокорреляцию (Льюнг-Бокс) для наблюдений вылова и обоих индексов дали незначимые p-значения (p > 0.05). Это означает, что остатки модели ведут себя предсказуемо и не нарушают ключевых предположений метода.

Ретроспективный анализ показал исключительно низкие значения Mohn’s rho (ρ для B/Bmsy = -0.0002, для F/Fmsy = 0.0029), что свидетельствует о высочайшей стабильности оценок модели и отсутствии ретроспективного смещения — редкий и прекрасный результат на реальных данных.

Оценки параметров популяции оказались биологически правдоподобными: темп роста r = 0.377 [0.289–0.491] год⁻¹, что характерно для видов со средней продуктивностью. Несущая способность среды K оценена в 189.6 [153.8–233.6] тыс. тонн. Максимальный устойчивый вылов (MSY) составил 17.8 [16.3–19.6] тыс. тонн в год.

Ключевой результат — оценка текущего состояния запаса. По состоянию на конец 2024 года запас находится в хорошем состоянии: относительная биомасса B/Bmsy = 1.17 [1.16–1.18], а промысловая смертность F/Fmsy = 0.10 [0.10–0.11]. Это означает, что запас превышает целевой уровень Bmsy, а промысловое давление существенно ниже пределього уровня Fmsy. Говоря управленческим языком, запас находится в «зеленой зоне» и имеет значительную устойчивость к потенциальным ошибкам управления.

14.4 Заключение по первому этапу анализа

Первый скрипт успешно выполнил свою задачу: мы перешли от сырых данных к параметризованной модели, дающей количественные оценки состояния запаса. Важно подчеркнуть, что это не конец работы, а только начало. Полученные оценки — это не истина в последней инстанции, а наиболее вероятное состояние системы given the data and the model.

Тот факт, что модель сошлась, прошла диагностику и дала биологически осмысленные результаты с узкими доверительными интервалами, вселяет осторожный оптимизм. Однако следует помнить, что модель — это упрощение. Например, мы зафиксировали параметр формы продукционной кривой (n = 2, модель Шефера), хотя в реальности он может отличаться. Мы использовали информативные априорные распределения для K и начальной биомассы, что помогло стабилизировать оценки, но одновременно внесло в анализ субъективный элемент.

Полученная картина благополучного состояния запаса выглядит правдоподобно на фоне снижения выловов в последнее десятилетие. Низкая промысловая смертность позволяет запасу восстанавливаться. Теперь, имея на руках оцененную модель, мы можем переходить к следующему шагу — исследованию различных сценариев управления и расчету потенциальных выловов, что и является содержанием последующих скриптов. По иронии судьбы, самая сложная часть — получение надежной модели — часто оказывается проще, чем последующее принятие управленческих решений на ее основе.