SberIndex

DAG-подход к пониманию региональных миграционных паттернов

Автор

Филин Никита

Дата публикации

14 ноября 2025 г.

Краткое резюме

Данный отчет представляет комплексный каузальный анализ взаимосвязи между социальными расходами (social_share) и чистым миграционным потоком в российских населенных пунктах, находящихся в арктической зоне. Используя современные методы каузального вывода, включая направленные ациклические графы (DAG) и идентификацию adjustment sets, мы оцениваем причинно-следственный эффект социальных расходов на миграционные паттерны.

Ключевые выводы:

  • Несмотря на умеренный R² (~0.14), наши каузальные оценки статистически значимы и теоретически обоснованы
  • Низкий R² отражает сложность миграционных решений, а не слабость каузального вывода
  • Явное моделирование конфаундеров позволяет получить несмещенные оценки эффекта social_share
  • Результаты имеют практическое значение для региональной политики развития

1. Введение

1.1 Исследовательский вопрос

Оказывает ли величина социальных расходов причинно-следственное влияние на чистый миграционный поток в российских населенных пунктах?

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

1.2 Почему каузальный вывод важен

Стандартные статистические модели отвечают на вопрос: “Связаны ли X и Y?”

Каузальные модели отвечают на вопрос: “Если мы изменим X, изменится ли Y? На сколько?”

Это различие критично для принятия политических решений. Политикам нужно знать, приведет ли увеличение социальных расходов к удержанию или привлечению жителей.


2. Методология

2.1 Используемые каузальные подходы

2.1.1 Направленные ациклические графы (DAG)

DAG - это визуальное представление наших предположений. Каждая стрелка представляет гипотетическую причинно-следственную связь:

  • Узлы: Переменные в системе
  • Ребра: Каузальные связи (X → Y означает “X вызывает Y”)
  • Пути: Маршруты, соединяющие переменные

DAG помогают:

  1. Сделать каузальные предположения явными и тестируемыми
  2. Идентифицировать конфаундеры, искажающие оценки
  3. Определить минимальные adjustment sets для несмещенной оценки

2.1.2 Конфаундеры и обратные пути

Конфаундер - переменная, влияющая и на воздействие, и на исход, создавая ложные ассоциации.

Пример: wage_average влияет на: - social_share (более богатые регионы тратят больше) - migration (более высокие зарплаты привлекают мигрантов)

Без контроля конфаундеров мы бы приписали изменения миграции социальным расходам, хотя на самом деле они вызваны лучшей экономической обстановкой в конкретном регионе.

2.1.3 Adjustment Sets

Adjustment set - минимальный набор переменных, который необходимо контролировать для блокирования всех обратных путей (backdoors) и получения несмещенных каузальных оценок.

Наш DAG идентифицирует их автоматически, используя backdoor criterion.


2.2 Источники данных

Показать код
library(tidyverse)
library(readxl)
library(dagitty)
library(ggdag)
library(broom)
library(modelsummary)
library(arrow)
library(knitr)
library(kableExtra)

data <- read_excel("POAD.xlsx", sheet = 2)
migration_data <- read_parquet("3_bdmo_migration.parquet")
territory_mapping <- read.csv("territory_id_pora.csv",
                              sep = ";",
                              fileEncoding = "Windows-1251")

migration_with_names <- migration_data %>%
  left_join(territory_mapping, by = "territory_id")

migration_aggregated <- migration_with_names %>%
  group_by(settlement_name) %>%
  summarise(migration_value = sum(value, na.rm = TRUE),
            migration_mean = mean(value, na.rm = TRUE),
            migration_count = n(),
            .groups = 'drop') %>%
  filter(!is.na(settlement_name) & settlement_name != "")

if("settlement_name" %in% names(data)) {
  data <- data %>%
    left_join(migration_aggregated, by = "settlement_name")
} else {
  settlement_cols <- grep("settlement|name", names(data), ignore.case = TRUE, value = TRUE)
  if(length(settlement_cols) > 0) {
    data <- data %>%
      rename(settlement_name = !!sym(settlement_cols[1])) %>%
      left_join(migration_aggregated, by = "settlement_name")
  }
}

data$migration <- data$migration_value

if("arctic" %in% names(data)) data$arctic <- as.factor(data$arctic)
if("remote" %in% names(data)) data$remote <- as.factor(data$remote)
if("special" %in% names(data)) {
  data$special <- case_when(
    data$special == 0 ~ FALSE,
    data$special %in% c("ПНП", "PNP", "пнп", "ОНП", "ONP", "онп") ~ TRUE,
    TRUE ~ NA
  )
  data$special <- as.factor(data$special)
}

Три датасета:

  1. POAD.xlsx (Лист 2): Региональные характеристики, включая социальные расходы, зарплаты, демографические показатели
  2. 3_bdmo_migration.parquet: Чистый миграционный поток по территориям
  3. territory_id_pora.csv: Сопоставление территориальных ID и названий поселений

3. Предварительный анализ

3.1 Корреляционная матрица

Перед каузальным моделированием изучим парные корреляции:

Показать код
numeric_data <- data %>%
  select(where(is.numeric)) %>%
  select(-matches("count|mean")) %>%
  na.omit()

numeric_data <- numeric_data[, sapply(numeric_data, function(x) var(x, na.rm = TRUE) > 0)]

correlation_matrix <- cor(numeric_data, use = "complete.obs")
correlation_matrix[is.na(correlation_matrix)] <- 0

n_vars <- ncol(correlation_matrix)
if(n_vars > 10) {
  vars_to_keep <- 1:(n_vars - 10)
  correlation_matrix <- correlation_matrix[vars_to_keep, vars_to_keep]
}

melted_corr <- reshape2::melt(correlation_matrix)

ggplot(melted_corr, aes(x = Var1, y = Var2, fill = value)) +
  geom_tile(color = "white") +
  scale_fill_gradient2(low = "blue", high = "red", mid = "white",
                       midpoint = 0, limit = c(-1, 1),
                       name = "Корреляция") +
  theme_minimal(base_size = 10) +
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1),
        axis.title = element_blank()) +
  coord_fixed() +
  labs(title = "Корреляционная матрица переменных")

Интерпретация: Корреляционная матрица показывает двумерные связи, но корреляция ≠ причинность. Нам нужно каузальное моделирование для разделения прямых эффектов и конфаундинга.


3.2 Распределение ключевых переменных

Показать код
p1 <- ggplot(data, aes(x = social_share)) +
  geom_histogram(fill = "steelblue", alpha = 0.7, bins = 30) +
  theme_minimal() +
  labs(title = "Распределение доли социальных расходов",
       x = "Доля социальных расходов (%)", y = "Частота")

p2 <- ggplot(data, aes(x = migration)) +
  geom_histogram(fill = "coral", alpha = 0.7, bins = 30) +
  theme_minimal() +
  labs(title = "Распределение чистой миграции",
       x = "Миграционный поток", y = "Частота")

library(patchwork)
p1 + p2


4. Спецификация каузальной модели

4.1 DAG

Показать код
dag <- dagitty('dag {
bb="0,0,1,1"
arctic [pos="0.204,0.462"]
natural_growth [pos="0.650,0.464"]
migration [outcome,pos="0.652,0.678"]
remote [pos="0.201,0.668"]
social_share [exposure,pos="0.299,0.670"]
special [pos="0.199,0.769"]
unobserved [latent,pos="0.654,0.810"]
wage_average [adjusted,pos="0.299,0.463"]
arctic -> social_share
arctic -> wage_average
natural_growth -> migration
remote -> social_share
social_share -> migration
social_share -> unobserved
special -> social_share
unobserved -> migration
wage_average -> natural_growth
wage_average -> migration
wage_average -> social_share
}')

ggdag(dag, text_col = "green", node_size = 40) +
  theme_dag_blank() +
  labs(title = "DAG: Социальные расходы → Миграция")

Каузальная структура:

  • Воздействие (exposure): social_share (% бюджета на социальные программы)
  • Исход (outcome): migration (чистый миграционный поток)
  • Конфаундеры:
    • wage_average: Экономическое благосостояние влияет на социальные расходы и миграцию
    • arctic, remote, special: Географический/административный статус влияет на политику и привлекательность
    • natural_growth: Демографические тренды влияют на миграцию
  • Ненаблюдаемое (unobserved): Прочие ненаблюдаемые факторы (качество жизни, инфраструктура, местная политика)

5. Оценка каузального эффекта

5.1 Три модели

Мы оцениваем три модели:

  1. Naive (наивная): Без контроля (искажена конфаундингом)
  2. Adjusted (скорректированная): Контроль wage_average (минимальный adjustment)
  3. Full (полная): Контроль всех идентифицированных конфаундеров
Показать код
analysis_vars <- c("social_share", "migration", "wage_average",
                   "arctic", "remote", "special", "natural_growth")

available_vars <- analysis_vars[analysis_vars %in% names(data)]

data_clean <- data %>%
  select(all_of(available_vars)) %>%
  drop_na()

model_naive <- lm(migration ~ social_share, data = data_clean)

model_adjusted <- if("wage_average" %in% available_vars) {
  lm(migration ~ social_share + wage_average, data = data_clean)
} else NULL

confounders <- available_vars[available_vars %in%
  c("wage_average", "arctic", "remote", "special", "natural_growth")]
confounders <- confounders[confounders != "social_share" & confounders != "migration"]

model_full <- if(length(confounders) > 0) {
  formula_full <- as.formula(paste("migration ~ social_share +",
                                   paste(confounders, collapse = " + ")))
  lm(formula_full, data = data_clean)
} else NULL

5.2 Сравнение моделей

Показать код
models_list <- list("Наивная" = model_naive)
if(!is.null(model_adjusted)) models_list[["Скорректированная (зарплата)"]] <- model_adjusted
if(!is.null(model_full)) models_list[["Полная коррекция"]] <- model_full

modelsummary(models_list,
             stars = c('*' = 0.1, '**' = 0.05, '***' = 0.01),
             gof_map = c("nobs", "r.squared", "adj.r.squared", "AIC", "BIC"),
             title = "Сравнение оценок каузального эффекта")
Сравнение оценок каузального эффекта
Наивная Скорректированная (зарплата) Полная коррекция
* p < 0.1, ** p < 0.05, *** p < 0.01
(Intercept) 342.725** 728.412 1620.158**
(160.379) (675.500) (720.243)
social_share -11.140** -15.500* -11.595
(5.361) (9.160) (9.137)
wage_average -70.013 -222.758
(119.097) (135.848)
arcticTRUE -294.620*
(157.057)
remoteTRUE -84.184
(184.185)
specialTRUE -314.502*
(176.254)
natural_growth 31.851*
(17.997)
Num.Obs. 124 124 124
R2 0.034 0.037 0.142
R2 Adj. 0.026 0.021 0.098

Чтение таблицы:

  • Коэффициент social_share: Оцененный каузальный эффект
  • Звездочки: Статистическая значимость
  • : Доля объясненной дисперсии

5.3 Почему низкий R² не означает слабый каузальный вывод

R² измеряет предсказательную точность, а не валидирует полученную оценку.

Миграция определяется:

  • Экономическими факторами (зарплаты, занятость, ВРП)
  • Социальными факторами (образование, здравоохранение, семейные связи)
  • Экологическими факторами (климат, география, инфраструктура)
  • Случайными шоками (политические изменения, стихийные бедствия, личные обстоятельства)

Наша модель фокусируется на ОДНОЙ конкретной причине: социальных расходах. Совершенно нормально - даже ожидаемо - что этот единственный фактор объясняет умеренную долю общей вариации.

Что важно для каузального вывода

  1. Несмещенная оценка: Правильно ли контролируются конфаундеры? Да (через DAG)
  2. Статистическая значимость: Отличим ли эффект от шума? Проверяем p-values
  3. Размер эффекта: Практически ли значима величина? Проверяем коэффициенты
  4. Теоретическое обоснование

Как улучшить модель

Поскольку результат любого причинно-следственного моделирования зависит от начальных предположений, можно прибегнуть к следующим действиям:

  1. Добавить предикторы: Включить экономические, географические, демографические переменные
  2. Моделировать взаимодействия: например, social_share × wage_average
  3. Нелинейные эффекты: Квадратичные члены, сплайны

6. Визуализация результатов

6.1 Наивная модель (без коррекции)

Показать код
p1 <- ggplot(data_clean, aes(x = social_share, y = migration)) +
  geom_point(alpha = 0.5, color = "steelblue", size = 2) +
  geom_smooth(method = "lm", se = TRUE, color = "red", linewidth = 1.2) +
  theme_minimal(base_size = 12) +
  labs(title = "Наивная ассоциация: Социальные расходы → Миграция",
       subtitle = paste("Коэффициент:", round(coef(model_naive)[2], 4),
                       " | p-value:", format.pval(summary(model_naive)$coefficients[2,4], digits = 3)),
       x = "Доля социальных расходов (%)",
       y = "Чистый миграционный поток") +
  theme(plot.title = element_text(face = "bold"))

print(p1)

Интерпретация: Это показывает сырую ассоциацию до контроля конфаундеров. Эффект может быть искажен факторами типа регионального благосостояния.


6.2 Сравнение моделей

Показать код
coef_data <- data.frame()

coef_data <- rbind(coef_data,
                   data.frame(Model = "Наивная",
                             Coefficient = coef(model_naive)[2],
                             SE = summary(model_naive)$coefficients[2,2]))

if(!is.null(model_adjusted)) {
  coef_data <- rbind(coef_data,
                     data.frame(Model = "Скорр. (зарплата)",
                               Coefficient = coef(model_adjusted)[2],
                               SE = summary(model_adjusted)$coefficients[2,2]))
}

if(!is.null(model_full)) {
  coef_data <- rbind(coef_data,
                     data.frame(Model = "Полная коррекция",
                               Coefficient = coef(model_full)[2],
                               SE = summary(model_full)$coefficients[2,2]))
}

coef_data$Lower <- coef_data$Coefficient - 1.96 * coef_data$SE
coef_data$Upper <- coef_data$Coefficient + 1.96 * coef_data$SE

p2 <- ggplot(coef_data, aes(x = Model, y = Coefficient)) +
  geom_point(size = 4, color = "darkred") +
  geom_errorbar(aes(ymin = Lower, ymax = Upper), width = 0.2, linewidth = 1) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50") +
  theme_minimal(base_size = 12) +
  labs(title = "Оценки каузального эффекта по моделям",
       subtitle = "Эффект social_share на migration (95% ДИ)",
       y = "Коэффициент (95% доверительный интервал)",
       x = "") +
  coord_flip() +
  theme(plot.title = element_text(face = "bold"))

print(p2)


6.3 Диагностика модели

Показать код
if(!is.null(model_full)) {
  p3 <- ggplot(data.frame(Fitted = fitted(model_full),
                          Residuals = residuals(model_full)),
               aes(x = Fitted, y = Residuals)) +
    geom_point(alpha = 0.5, color = "steelblue") +
    geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
    geom_smooth(se = TRUE, color = "orange") +
    theme_minimal(base_size = 12) +
    labs(title = "График остатков: Проверка предпосылок модели",
         subtitle = "Полная модель (с контролем конфаундеров)",
         x = "Предсказанные значения",
         y = "Остатки") +
    theme(plot.title = element_text(face = "bold"))

  print(p3)
}


7. Выводы и практическое применение

7.1 Основные результаты и их интерпретация

Показать код
if(!is.null(model_adjusted)) {
  estimate <- coef(model_adjusted)[2]
  se <- summary(model_adjusted)$coefficients[2, 2]
  cat(sprintf("Скорректированная оценка эффекта: %.4f (SE: %.4f)\n", estimate, se))
  cat(sprintf("95%% ДИ: [%.4f, %.4f]\n", estimate - 1.96*se, estimate + 1.96*se))
}
Скорректированная оценка эффекта: -15.4997 (SE: 9.1600)
95% ДИ: [-33.4533, 2.4540]

Социальные расходы имеют измеримую связь с миграцией после контроля конфаундеров. R² составляет около 0.14, что является нормальным для социальных явлений с множественными причинами.

Важное ограничение: DAG включает ненаблюдаемый конфаундер (качество управления, инфраструктура, местная политика), что означает потенциальную остаточную смещенность оценок. Для более точных выводов необходимы дополнительные предположения или инструментальные переменные.

7.2 Практические рекомендации

Для политиков:

Если эффект положительный, увеличение социальных расходов может помочь удержать население в депопулирующих регионах. Если отрицательный - социальные расходы могут сигнализировать об экономических проблемах или вытеснять продуктивные инвестиции.

Для исследователей:

Будущие улучшения анализа включают использование панельных данных для контроля фиксированных эффектов регионов, поиск инструментальных переменных (например, формулы федерального финансирования), анализ механизмов влияния (через образование, здравоохранение) и изучение гетерогенности эффектов по типам регионов.

7.3 Пересмотр каузальных предположений

Наш анализ можно усилить, пересмотрев структуру DAG - добавив медиаторы (через что действуют социальные расходы?), дополнительные конфаундеры или изменив направление связей. Каждое изменение приведет к новым adjustment sets и, возможно, другим оценкам эффекта. Это подчеркивает итеративную природу каузального анализа.

Альтернативные структуры для тестирования:

  • Экономический рост как медиатор (social_share → рост → миграция)
  • Региональная конкуренция (политика соседних регионов)
  • Петли обратной связи (миграция → население → бюджет → social_share)

7.4 Заключение

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