Для анализа данных нам потребуются следующие пакеты:

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.0     ✔ readr     2.2.0
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.2     ✔ tibble    3.3.1
## ✔ lubridate 1.9.5     ✔ tidyr     1.3.2
## ✔ purrr     1.2.1     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(psych)
## 
## Присоединяю пакет: 'psych'
## 
## Следующие объекты скрыты от 'package:ggplot2':
## 
##     %+%, alpha
library(DescTools)
## 
## Присоединяю пакет: 'DescTools'
## 
## Следующие объекты скрыты от 'package:psych':
## 
##     AUC, ICC, SD
library(nortest)
library(ggpubr)
library(ggstatsplot)
## You can cite this package as:
##      Patil, I. (2021). Visualizations with statistical details: The 'ggstatsplot' approach.
##      Journal of Open Source Software, 6(61), 3167, doi:10.21105/joss.03167

Задание 1. Провести комплексную проверку на нормальность переменной Sepal.Length из набора `iris.

data(iris)
str(iris$Sepal.Length)
##  num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...

Проведем анализ базовых описательных статистик, также расширенный статистический анализ

summary(iris$Sepal.Length)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   4.300   5.100   5.800   5.843   6.400   7.900
psych::describe(iris$Sepal.Length)
##    vars   n mean   sd median trimmed  mad min max range skew kurtosis   se
## X1    1 150 5.84 0.83    5.8    5.81 1.04 4.3 7.9   3.6 0.31    -0.61 0.07

Из полученных данных, можем отметить, что Среднее (5.843) и медиана (5.800) очень близки Разница всего ~0.043, что указывает на симметричность распределения. Mean > Median (незначительно) — очень слабая положительная асимметрия. Эксцесс (Kurtosis) составляет -0.61, Стандартная ошибка среднего (se) равна 0.07, что свидетельствует о высокой точности оценки среднего по выборке. Истинное среднее значение в генеральной совокупности

hist(iris$Sepal.Length, 
     col = "steelblue", 
     xlab = "Длина чашелистика (см)", 
     main = "Распределение длины чашелистика (iris)")

Видим, что длина чашелистника вариьруется от 4 до 8, наиболее часто 4,5-6,5 см, нет сильных выбросов

ggplot(iris, aes(x = Sepal.Length)) + 
  geom_density(fill = "lightblue", alpha = 0.7) +
  geom_vline(aes(xintercept = mean(Sepal.Length)), 
             color = "red", linetype = "dashed", size = 1) +
  labs(title = "Плотность распределения длины чашелистика",
       x = "Длина чашелистика (см)", 
       y = "Плотность") +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

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

qqnorm(iris$Sepal.Length, main = "Q-Q plot для Sepal.Length")
qqline(iris$Sepal.Length, col = "steelblue", lwd = 2)

Диаграмма рассеивания также демонстрируют, что значения распределеются близко с прямой

boxplot(iris$Sepal.Length, 
        col = "steelblue", 
        main = "Ящичная диаграмма для Sepal.Length",
        ylab = "Длина чашелистика (см)")

Далее проведем стастистический тест на нормальность

# Тест Шапиро-Уилка
shapiro.test(iris$Sepal.Length)
## 
##  Shapiro-Wilk normality test
## 
## data:  iris$Sepal.Length
## W = 0.97609, p-value = 0.01018

Несмотря на то, что визуально распределение выглядит симметричным (среднее 5.84 близко к медиане 5.80), статистический тест Шапиро-Уилка указывает на значимое отклонение от нормального распределения. Это означает, что длина чашелистика в наборе данных iris статистически значимо отличается от нормального распределения (при уровне значимости α = 0.05).

Рассмторим доверительные интервалы для асимметрии и эксцесса

Skew(iris$Sepal.Length, na.rm = TRUE, conf.level = 0.95)
##       skew     lwr.ci     upr.ci 
## 0.30864073 0.04045809 0.53725449
Kurt(iris$Sepal.Length, method = 2, conf.level = 0.95)
##       kurt     lwr.ci     upr.ci 
## -0.5520640 -0.8883591 -0.1312562

Оба показателя одновременно отклоняются от нуля, что объясняет, почему тест Шапиро-Уилка дал значимый результат (p = 0.010). Даже умеренные отклонения по обоим параметрам суммарно дают достаточное свидетельство против нормальности.

Задание 2. Провести одномерный анализ по переменным V12, V13, V15. Сделать двумерный анализ по региону и возрасту.

install.packages(“DataExplorer”) library(DataExplorer) introduce(df)

library(haven)
df<-read_sav("files/База_КлимРиск_2023.sav")

Проведем одномерный анализ

summary(df$V12)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##    1.00    2.00    2.00   10.72    3.00   99.00      14
summary(df$V13)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##    1.00    1.00    1.00    1.54    2.00    3.00      23
summary(df$V15)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   1.000   1.000   1.000   1.516   2.000   3.000      20

Рассмторим чатотные таблицы переменных

v13_table <- table(df$V13, useNA = "ifany")
print(v13_table)
## 
##    1    2    3 <NA> 
##  575  149  166   23
print(round(prop.table(v13_table) * 100, 2))
## 
##     1     2     3  <NA> 
## 62.98 16.32 18.18  2.52
v15_table <- table(df$V15, useNA = "ifany")
print(v15_table)
## 
##    1    2    3 <NA> 
##  586  153  154   20
print(round(prop.table(v15_table) * 100, 2))
## 
##     1     2     3  <NA> 
## 64.18 16.76 16.87  2.19
v12_table <- table(df$V12, useNA = "ifany")
print(v12_table)
## 
##    1    2    3   88   99 <NA> 
##  190  437  189   19   64   14
print(round(prop.table(v12_table) * 100, 2))
## 
##     1     2     3    88    99  <NA> 
## 20.81 47.86 20.70  2.08  7.01  1.53

Заметим, что у переменной v12 есть ответ 99, который следует заменить

df$V12_clean <- ifelse(df$V12 == 99, NA, df$V12)
summary(df$V12_clean)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   1.000   2.000   2.000   3.956   2.000  88.000      78

Построим рафики переменныхдля более наглядного анализа.

hist(df$V12_clean, col="steelblue", main="V12", xlab="V12")

barplot(table(df$V13), col="darkgreen", main="V13")

barplot(table(df$V15), col="coral", main="V15")

Далее проведем двумерный анализ. Первоначально создадим возрастные группы

df$age_group <- cut(df$age, breaks = c(0, 30, 50, 100), 
                    labels = c("До 30", "31-50", "50+"))

Составим таблицы по регионам

table(df$V13, df$Region)
##    
##       1   2   3   4
##   1 106 327 142   0
##   2  84  47  18   0
##   3  76  61  29   0
table(df$V15, df$Region)
##    
##       1   2   3   4
##   1 133 310 143   0
##   2  57  76  20   0
##   3  76  51  27   0

Алтайский край отличается более равномерным распределением ответов по всем категориям. Республика Алтай и Республика Тыва демонстрируют схожую структуру с доминированием категории 1.Есть пустой регион - Монголия.

Проведем хи-квадрат тест.

chisq.test(table(df$V13, df$Region))
## Warning in chisq.test(table(df$V13, df$Region)): аппроксимация на основе
## хи-квадрат может быть неправильной
## 
##  Pearson's Chi-squared test
## 
## data:  table(df$V13, df$Region)
## X-squared = NaN, df = 6, p-value = NA
chisq.test(table(df$V15, df$age_group))
## 
##  Pearson's Chi-squared test
## 
## data:  table(df$V15, df$age_group)
## X-squared = 24.865, df = 4, p-value = 5.356e-05

В таблице есть нулевая строка или столбец, поэтому тест по регионам не корректен. Тест V15 по возрасту p < 0.001 → статистически значимая связь. Возраст влияет на ответы в V15. Различия между возрастными группами не случайны

Задание 3. Проанализировать переменные с множественным выбором V14 и V16, также сделать двумерный анализ по региону. По всем видам анализа сделать таблицы и графики.

library(dplyr)
library(tidyr)
library(questionr)
## 
## Присоединяю пакет: 'questionr'
## Следующий объект скрыт от 'package:psych':
## 
##     describe
library(ggplot2)
library(forcats)

для более точного анализа, очистим данныеот региона 4 - Монголия

df_clean <- df %>%
  filter(!is.na(Region), Region != 4) %>%
  mutate(Region = droplevels(as.factor(Region)))

Создадим возрастные группы

df_clean$age_group <- cut(df_clean$age, 
                          breaks = c(0, 30, 50, 100), 
                          labels = c("До 30 лет", "31-50 лет", "Старше 50"))

cat("Размер очищенных данных:", nrow(df_clean), "наблюдений\n")
## Размер очищенных данных: 912 наблюдений
cat("Регионы:", paste(levels(df_clean$Region), collapse = ", "), "\n")
## Регионы: 1, 2, 3

Проведем для начала одномерный анализ переменной V14, построим таблицу

V14 <- df_clean %>% 
  select(V14_1, V14_2, V14_3, V14_4, V14_5, V14_6, 
         V14_7, V14_8, V14_9, V14_10, V14_88)


V14_clean <- as.data.frame(lapply(V14, function(x) {
  if(is.numeric(x)) {
    factor(x, levels = c(0, 1), labels = c("нет", "да"))
  } else {
    x
  }
}))

V14_freq <- multi.table(V14_clean, true.codes = list("да"), freq = TRUE)


V14_results <- data.frame(
  Переменная = rownames(V14_freq),
  Частота = V14_freq[, 1],
  Процент = round(V14_freq[, 2], 1)
) %>%
  arrange(desc(Частота))

print(V14_results)
##        Переменная Частота Процент
## V14_2       V14_2     436    47.8
## V14_1       V14_1     428    46.9
## V14_10     V14_10     381    41.8
## V14_5       V14_5     310    34.0
## V14_4       V14_4     266    29.2
## V14_7       V14_7     211    23.1
## V14_3       V14_3     102    11.2
## V14_8       V14_8      64     7.0
## V14_6       V14_6      58     6.4
## V14_9       V14_9      42     4.6
## V14_88     V14_88      23     2.5

Наиболее популярными оказались варианты V14_2 (47.8%), V14_1 (46.9%) и V14_10 (41.8%). Варианты V14_5, V14_4 и V14_7 выбрали от 23% до 34% респондентов. Остальные варианты встречаются значительно реже — менее 12%, причем вариант ‘Другое’ (V14_88) отметили лишь 2.5% опрошенных, что свидетельствует о полноте предложенного списка. Построим график.

library(ggplot2)
V14_results %>%
  mutate(Переменная = factor(Переменная, levels = Переменная[order(Процент)])) %>%
  ggplot(aes(x = Переменная, y = Процент)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  geom_text(aes(label = paste0(Процент, "%")), hjust = -0.1, size = 3) +
  coord_flip() +
  labs(title = "V14: множественный выбор",
       x = "", y = "Процент респондентов (%)") +
  theme_minimal() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))

Далее проведем двумерный анализ по регионам для рассматриваемой переменной.

V14_region <- cross.multi.table(V14_clean, df_clean$Region, 
                                 true.codes = list("да"), freq = TRUE)

print(V14_region)
##           1    2    3
## V14_1  63.5 43.3 31.8
## V14_2  25.5 63.0 44.3
## V14_3  17.9  9.4  5.7
## V14_4  23.4 29.6 36.5
## V14_5  22.6 39.7 37.0
## V14_6   2.2  9.0  6.2
## V14_7  17.5 26.7 22.9
## V14_8   7.7  8.1  3.6
## V14_9   1.8  6.7  3.6
## V14_10 34.7 47.3 39.1
## V14_88  0.0  4.5  1.6
V14_region_df <- as.data.frame(V14_region)
V14_region_df$Переменная <- rownames(V14_region_df)
for(reg in names(V14_region_df)[1:3]) {
  cat("\n---", reg, "---\n")
  top3 <- V14_region_df %>%
    select(Переменная, all_of(reg)) %>%
    arrange(desc(.data[[reg]])) %>%
    head(3)
  print(top3)
}
## 
## --- 1 ---
##        Переменная    1
## V14_1       V14_1 63.5
## V14_10     V14_10 34.7
## V14_2       V14_2 25.5
## 
## --- 2 ---
##        Переменная    2
## V14_2       V14_2 63.0
## V14_10     V14_10 47.3
## V14_1       V14_1 43.3
## 
## --- 3 ---
##        Переменная    3
## V14_2       V14_2 44.3
## V14_10     V14_10 39.1
## V14_5       V14_5 37.0

также проанализируем переменную V16. Для начала проведем одномерный анализ.

V16 <- df_clean %>% 
  select(V16_1, V16_2, V16_3, V16_4, V16_5, V16_6, 
         V16_7, V16_8, V16_9, V16_88)

cat("Количество переменных в блоке V16:", ncol(V16), "\n")
## Количество переменных в блоке V16: 10
print(names(V16))
##  [1] "V16_1"  "V16_2"  "V16_3"  "V16_4"  "V16_5"  "V16_6"  "V16_7"  "V16_8" 
##  [9] "V16_9"  "V16_88"
# Преобразуем все в факторы с правильными метками
V16_clean <- as.data.frame(lapply(V16, function(x) {
  if(is.numeric(x)) {
    
    
    if(all(unique(x[!is.na(x)]) %in% c(0, 1))) {
      factor(x, levels = c(0, 1), labels = c("нет", "да"))
    } else {
      as.factor(x)
    }
  } else if(is.character(x)) {
    as.factor(x)
  } else {
    x
  }
}))
V16_freq <- multi.table(V16_clean, true.codes = list("да"), freq = TRUE)

V16_results <- data.frame(
  Переменная = rownames(V16_freq),
  Частота = V16_freq[, 1],
  Процент = round(V16_freq[, 2], 1)
) %>%
  arrange(desc(Частота))

print(V16_results)
##        Переменная Частота Процент
## V16_7       V16_7     406    44.5
## V16_3       V16_3     392    43.0
## V16_1       V16_1     326    35.7
## V16_2       V16_2     290    31.8
## V16_8       V16_8     221    24.2
## V16_5       V16_5     173    19.0
## V16_9       V16_9     142    15.6
## V16_6       V16_6      80     8.8
## V16_4       V16_4      57     6.2
## V16_88     V16_88      34     3.7

Построим график для данной таблицы.

p1 <- V16_results %>%
  mutate(Переменная = fct_reorder(Переменная, Процент)) %>%
  ggplot(aes(x = Переменная, y = Процент, fill = Процент)) +
  geom_bar(stat = "identity", width = 0.7) +
  geom_text(aes(label = paste0(Процент, "%")), 
            hjust = -0.1, size = 3.5) +
  coord_flip() +
  scale_fill_gradient(low = "lightcoral", high = "darkred") +
  labs(title = "Распределение ответов на вопрос V16",
       subtitle = "Процент респондентов, выбравших каждый вариант",
       x = "", y = "Процент (%)") +
  theme_minimal() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold", hjust = 0.5, size = 14))

print(p1)

Анализ вопроса V16 показал, что наиболее распространенными являются варианты V16_7 (44.5%), V16_3 (43.0%) и V16_1 (35.7%). Высокую популярность (более 30%) также имеет вариант V16_2 (31.8%). Варианты V16_8, V16_5 и V16_9 выбрали от 15% до 24% респондентов. Наименее популярны варианты V16_6 (8.8%), V16_4 (6.2%) и V16_88 (3.7%). Низкий процент выбора варианта ‘Другое’ (3.7%) свидетельствует о том, что предложенный перечень хорошо отражает реальные практики респондентов.

Проведем двумерный анализ по региону.

V16_region <- cross.multi.table(V16_clean, df_clean$Region, 
                                 true.codes = list("да"), 
                                 freq = TRUE)

V16_region_df <- as.data.frame(V16_region)
V16_region_df$Переменная <- rownames(V16_region_df)

print(V16_region_df)
##           1    2    3 Переменная
## V16_1  23.7 45.3 30.7      V16_1
## V16_2  39.1 31.4 22.4      V16_2
## V16_3  60.2 34.1 39.1      V16_3
## V16_4   0.7  8.3  9.4      V16_4
## V16_5   6.9 27.6 16.1      V16_5
## V16_6   2.6 14.8  3.6      V16_6
## V16_7  29.2 52.2 48.4      V16_7
## V16_8  26.6 26.5 15.6      V16_8
## V16_9  20.8 15.9  7.3      V16_9
## V16_88  0.4  5.6  4.2     V16_88
regions <- names(V16_region_df)[1:3]
for(reg in regions) {
  cat("\n", gsub("\\.", " ", reg), ":\n")
  top3 <- V16_region_df %>%
    select(Переменная, all_of(reg)) %>%
    arrange(desc(.data[[reg]])) %>%
    head(3)
  
  top3$Процент <- paste0(round(top3[[reg]], 1), "%")
  print(top3[, c("Переменная", "Процент")])
}
## 
##  1 :
##       Переменная Процент
## V16_3      V16_3   60.2%
## V16_2      V16_2   39.1%
## V16_7      V16_7   29.2%
## 
##  2 :
##       Переменная Процент
## V16_7      V16_7   52.2%
## V16_1      V16_1   45.3%
## V16_3      V16_3   34.1%
## 
##  3 :
##       Переменная Процент
## V16_7      V16_7   48.4%
## V16_3      V16_3   39.1%
## V16_1      V16_1   30.7%

Региональный анализ V16 выявил существенные различия в структуре ответов. В Алтайском крае доминирует вариант V16_3 (60.2%), в Республике Алтай — V16_7 (52.2%) и V16_1 (45.3%), в Республике Тыва — V16_7 (48.4%) и V16_3 (39.1%). Наибольший разброс между регионами наблюдается по вариантам V16_3 (26.1%), V16_1 (21.6%) и V16_5 (20.7%), что указывает на значительную региональную специфику.

top5_vars <- V16_results %>% head(5) %>% pull(Переменная)

V16_region_long <- V16_region_df %>%
  filter(Переменная %in% top5_vars) %>%
  select(Переменная, `1`, `2`, `3`) %>%  # обратные кавычки для цифр
  pivot_longer(cols = -Переменная,
               names_to = "Регион",
               values_to = "Процент") %>%
  mutate(Регион = case_when(
    Регион == "1" ~ "Алтайский край",
    Регион == "2" ~ "Республика Алтай",
    Регион == "3" ~ "Республика Тыва"
  ))

# График
p2 <- ggplot(V16_region_long, aes(x = Переменная, y = Процент, fill = Регион)) +
  geom_bar(stat = "identity", position = "dodge", width = 0.7) +
  geom_text(aes(label = paste0(round(Процент, 1), "%")),
            position = position_dodge(width = 0.7),
            vjust = -0.3, size = 3) +
  scale_fill_manual(values = c("Алтайский край" = "#1f77b4",
                                "Республика Алтай" = "#ff7f0e",
                                "Республика Тыва" = "#2ca02c")) +
  labs(title = "Топ-5 вариантов V16 по регионам",
       x = "", y = "Процент (%)",
       fill = "Регион") +
  theme_minimal() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 14),
        legend.position = "bottom",
        axis.text.x = element_text(angle = 45, hjust = 1))

print(p2)

Данные, представленные, на диаграмме, подверждают сделанные выше выводы.