5 Неделя 2, День 4

5.1 Многомерные методы анализа данных

Сегодняшнее занятие будет посвящено многомерным методам анализа данных - методам работы с данными, в которых много колонок. Мы уже сталкивались с некоторыми многомерными методами, такими как множественная линейная регрессия. Поэтому вы знаете, что многомерность создает новые проблемы. Например, при множественных корреляциях или попарных сравнениях возникает проблема множественных сравнений, а при использовании множественной регрессии лишние предикторы могут ловить только шум и приводить к переобучению (если говорить в терминах машинного обучения). Короче говоря, больше - не значит лучше. Нужно четко понимать, зачем мы используем данные и что пытаемся измерить.

Однако в некоторых случаях мы в принципе не можем ничего интересного сделать с маленьким набором переменных. Много ли мы можем измерить личностным тестом с одним единственным вопросом? Можем ли мы точно оценить уровень интеллекта по успешности выполнения одного единственного задания? Очевидно, что нет. Более того, даже концепция интеллекта в современном его представлении появилась во многом благодаря разработке многомерных методов анализа! Ну или наоборот: исследования интеллекта подстегнули развитие многомерных методов.

Наши данные как раз очень многомерные. Это данные опроса с использованием очень большого количества вопросов. Мы возьмем только колонки, содержащие в названии “Op” - это ответы на всякие мировоззренческие вопросы, а это значит, что на них нет правильного ответа, а разные люди будут отвечать по-разному. Эти вопросы связаны с отношением к науке и технологиям, к “научному мировоззрению”. Например, “Scientific development is essential for improving people’s lives” и “Consuming genetically modified (GMO) food is perfectly safe”. Очевидно, что ответы на все эти вопросы будут как-то скоррелированы (положительно или отрицательно). С помощью таких методов как анализ главных компонент и факторный анализ можно уменьшить количество переменных и попытаться понять, что стоит на паттерном различий в ответах респондентов.

Самостоятельное задание:

  1. Сделайте data.table opinion, в котором будут только колонки с "OP" или "Op" в названии.
library(data.table)
data <- fread("data/iGLAS for R course.csv")

Для этого можно воспользоваться регулярными выражениями!

op <- grep("iO[pP]", names(data), value = TRUE)
opinion <- data[, ..op]
  1. Удалите все колонки, где слишком много NA, потом удалите строчки с NA:

Разобьем задачу на части: сначала создадим функцию, которая считает частоту NA в колонке:

frac_na <- function(x) mean(is.na(x))

Потом применим эту фунцкию, чтобы выделить названия колонок, в которых NA меньше половины:

not_na_op <- sapply(opinion, frac_na) < 0.5
opinion <- opinion[, ..not_na_op]

Осталось удалить относительно небольшое количество строчек с NA:

opinion <- opinion[complete.cases(opinion),]
  1. Посчитайте матрицу корреляций opinion:

Напоминаю, что это можно сделать несколькими способами. Конечно, можно просто коррелировать каждую пару переменных с каждой:

opinion[, cor.test(iOp01, iOp03)]
## 
##  Pearson's product-moment correlation
## 
## data:  iOp01 and iOp03
## t = 4.3619, df = 4981, p-value = 1.316e-05
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
##  0.03397746 0.08929939
## sample estimates:
##        cor 
## 0.06168581

Из этих данных можно доставать значений корреляций - и так повторить для каждой пары переменных. Это не очень удобно. Можно сделать проще - с помощью функции cor() посчитать всю матрицу корреляций.

opinion_cor <- cor(opinion)
opinion_cor
##                iOp01       iOp03       iOp04       iOp10        iOp11
## iOp01    1.000000000  0.06168581 -0.09790765  0.09710549 -0.005725227
## iOp03    0.061685805  1.00000000 -0.11774907  0.38227617 -0.125308613
## iOp04   -0.097907651 -0.11774907  1.00000000 -0.30967796  0.265454316
## iOp10    0.097105491  0.38227617 -0.30967796  1.00000000 -0.237845009
## iOp11   -0.005725227 -0.12530861  0.26545432 -0.23784501  1.000000000
## iOp02.5 -0.032672387 -0.10008960  0.30204766 -0.22103331  0.211416777
## iOp42    0.181658491  0.04159865  0.04141262 -0.03209103  0.082770905
## iOp43   -0.003794763 -0.02254635  0.12403584 -0.05257601  0.132697538
## iOp46    0.052547325  0.17397235 -0.14585023  0.22097407 -0.086064297
## iOp45    0.134881909  0.14934259 -0.41026076  0.28213000 -0.192350246
## iOP13.5 -0.040780421 -0.19078695  0.32713384 -0.29964666  0.215998965
## iOp44    0.004436168  0.03011891  0.07615861 -0.04030566  0.113388468
## iOp07.5  0.027425667 -0.07286297  0.18466025 -0.15426662  0.259294932
## iOp47   -0.012785049 -0.07423265  0.33009538 -0.27045062  0.312349968
## iOP12.5  0.168927842  0.12041632 -0.08985733  0.14446427 -0.068279608
##             iOp02.5        iOp42        iOp43       iOp46        iOp45
## iOp01   -0.03267239  0.181658491 -0.003794763  0.05254732  0.134881909
## iOp03   -0.10008960  0.041598655 -0.022546352  0.17397235  0.149342590
## iOp04    0.30204766  0.041412616  0.124035835 -0.14585023 -0.410260758
## iOp10   -0.22103331 -0.032091028 -0.052576008  0.22097407  0.282129998
## iOp11    0.21141678  0.082770905  0.132697538 -0.08606430 -0.192350246
## iOp02.5  1.00000000  0.136295690  0.124001544 -0.15602734 -0.209353362
## iOp42    0.13629569  1.000000000  0.232244315 -0.03223088 -0.006915756
## iOp43    0.12400154  0.232244315  1.000000000 -0.05692068 -0.047643316
## iOp46   -0.15602734 -0.032230885 -0.056920680  1.00000000  0.173623846
## iOp45   -0.20935336 -0.006915756 -0.047643316  0.17362385  1.000000000
## iOP13.5  0.39518963  0.134509434  0.098597768 -0.15017812 -0.214800604
## iOp44    0.07653149  0.099007929  0.108009257 -0.03229476 -0.046154797
## iOp07.5  0.23017497  0.207500065  0.248239874 -0.10794585 -0.083293739
## iOp47    0.25021173  0.161576936  0.113803852 -0.10644965 -0.241338330
## iOP12.5 -0.07638594  0.094466167 -0.032880878  0.14201499  0.093801862
##             iOP13.5        iOp44     iOp07.5        iOp47      iOP12.5
## iOp01   -0.04078042  0.004436168  0.02742567 -0.012785049  0.168927842
## iOp03   -0.19078695  0.030118914 -0.07286297 -0.074232647  0.120416318
## iOp04    0.32713384  0.076158609  0.18466025  0.330095376 -0.089857326
## iOp10   -0.29964666 -0.040305663 -0.15426662 -0.270450622  0.144464274
## iOp11    0.21599896  0.113388468  0.25929493  0.312349968 -0.068279608
## iOp02.5  0.39518963  0.076531485  0.23017497  0.250211729 -0.076385936
## iOp42    0.13450943  0.099007929  0.20750007  0.161576936  0.094466167
## iOp43    0.09859777  0.108009257  0.24823987  0.113803852 -0.032880878
## iOp46   -0.15017812 -0.032294760 -0.10794585 -0.106449653  0.142014989
## iOp45   -0.21480060 -0.046154797 -0.08329374 -0.241338330  0.093801862
## iOP13.5  1.00000000  0.100923055  0.21146886  0.315878637 -0.063165249
## iOp44    0.10092306  1.000000000  0.10301330  0.103123900 -0.014437449
## iOp07.5  0.21146886  0.103013296  1.00000000  0.296110105 -0.008059640
## iOp47    0.31587864  0.103123900  0.29611011  1.000000000  0.005139235
## iOP12.5 -0.06316525 -0.014437449 -0.00805964  0.005139235  1.000000000

Ну а можно воспользоваться пакетом "psych", который создаст специальный объект классов "psych" и "corr.test". Внутри этого объекта будут и матрица корреляций и матрица p-values.

library(psych)
opinion_corr <- corr.test(opinion)
class(opinion_corr)
## [1] "psych"     "corr.test"
class(opinion_corr$r)
## [1] "matrix"

Если Вы решите проверить, совпадают ли результаты этих двух способов, то увидите, что, внезапно, нет:

opinion_corr$r == opinion_cor
##         iOp01 iOp03 iOp04 iOp10 iOp11 iOp02.5 iOp42 iOp43 iOp46 iOp45
## iOp01    TRUE FALSE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE FALSE FALSE
## iOp03   FALSE  TRUE FALSE  TRUE  TRUE   FALSE  TRUE FALSE  TRUE FALSE
## iOp04    TRUE FALSE  TRUE FALSE FALSE   FALSE  TRUE FALSE  TRUE FALSE
## iOp10    TRUE  TRUE FALSE  TRUE FALSE   FALSE FALSE FALSE FALSE  TRUE
## iOp11    TRUE  TRUE FALSE FALSE  TRUE   FALSE  TRUE FALSE FALSE  TRUE
## iOp02.5  TRUE FALSE FALSE FALSE FALSE    TRUE FALSE FALSE  TRUE  TRUE
## iOp42    TRUE  TRUE  TRUE FALSE  TRUE   FALSE  TRUE  TRUE FALSE  TRUE
## iOp43    TRUE FALSE FALSE FALSE FALSE   FALSE  TRUE  TRUE  TRUE  TRUE
## iOp46   FALSE  TRUE  TRUE FALSE FALSE    TRUE FALSE  TRUE  TRUE FALSE
## iOp45   FALSE FALSE FALSE  TRUE  TRUE    TRUE  TRUE  TRUE FALSE  TRUE
## iOP13.5 FALSE  TRUE FALSE  TRUE  TRUE   FALSE  TRUE FALSE  TRUE  TRUE
## iOp44    TRUE FALSE  TRUE FALSE FALSE   FALSE  TRUE FALSE  TRUE FALSE
## iOp07.5  TRUE FALSE FALSE FALSE  TRUE    TRUE FALSE FALSE  TRUE  TRUE
## iOp47   FALSE  TRUE  TRUE FALSE  TRUE   FALSE  TRUE FALSE  TRUE  TRUE
## iOP12.5 FALSE FALSE FALSE FALSE FALSE    TRUE FALSE  TRUE  TRUE FALSE
##         iOP13.5 iOp44 iOp07.5 iOp47 iOP12.5
## iOp01     FALSE  TRUE    TRUE FALSE   FALSE
## iOp03      TRUE FALSE   FALSE  TRUE   FALSE
## iOp04     FALSE  TRUE   FALSE  TRUE   FALSE
## iOp10      TRUE FALSE   FALSE FALSE   FALSE
## iOp11      TRUE FALSE    TRUE  TRUE   FALSE
## iOp02.5   FALSE FALSE    TRUE FALSE    TRUE
## iOp42      TRUE  TRUE   FALSE  TRUE   FALSE
## iOp43     FALSE FALSE   FALSE FALSE    TRUE
## iOp46      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp45      TRUE FALSE    TRUE  TRUE   FALSE
## iOP13.5    TRUE FALSE    TRUE FALSE    TRUE
## iOp44     FALSE  TRUE    TRUE FALSE   FALSE
## iOp07.5    TRUE  TRUE    TRUE  TRUE   FALSE
## iOp47     FALSE FALSE    TRUE  TRUE   FALSE
## iOP12.5    TRUE FALSE   FALSE FALSE    TRUE

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

round(opinion_corr$r,2) == round(opinion_cor,2)
##         iOp01 iOp03 iOp04 iOp10 iOp11 iOp02.5 iOp42 iOp43 iOp46 iOp45
## iOp01    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp03    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp04    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp10    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp11    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp02.5  TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp42    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp43    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp46    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp45    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOP13.5  TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp44    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp07.5  TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOp47    TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
## iOP12.5  TRUE  TRUE  TRUE  TRUE  TRUE    TRUE  TRUE  TRUE  TRUE  TRUE
##         iOP13.5 iOp44 iOp07.5 iOp47 iOP12.5
## iOp01      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp03      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp04      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp10      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp11      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp02.5    TRUE  TRUE    TRUE  TRUE    TRUE
## iOp42      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp43      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp46      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp45      TRUE  TRUE    TRUE  TRUE    TRUE
## iOP13.5    TRUE  TRUE    TRUE  TRUE    TRUE
## iOp44      TRUE  TRUE    TRUE  TRUE    TRUE
## iOp07.5    TRUE  TRUE    TRUE  TRUE    TRUE
## iOp47      TRUE  TRUE    TRUE  TRUE    TRUE
## iOP12.5    TRUE  TRUE    TRUE  TRUE    TRUE

Давайте отдельно посмотрим на матрицу p-values (в нижнем углу - сами p-values, в верхнем - с коррекцией на множество сравнений):

round(opinion_corr$p, 3)
##         iOp01 iOp03 iOp04 iOp10 iOp11 iOp02.5 iOp42 iOp43 iOp46 iOp45
## iOp01   0.000 0.000 0.000 0.000     1   0.324 0.000 1.000 0.005 0.000
## iOp03   0.000 0.000 0.000 0.000     0   0.000 0.066 1.000 0.000 0.000
## iOp04   0.000 0.000 0.000 0.000     0   0.000 0.066 0.000 0.000 0.000
## iOp10   0.000 0.000 0.000 0.000     0   0.000 0.324 0.005 0.000 0.000
## iOp11   0.686 0.000 0.000 0.000     0   0.000 0.000 0.000 0.000 0.000
## iOp02.5 0.021 0.000 0.000 0.000     0   0.000 0.000 0.000 0.000 0.000
## iOp42   0.000 0.003 0.003 0.023     0   0.000 0.000 0.000 0.324 1.000
## iOp43   0.789 0.112 0.000 0.000     0   0.000 0.000 0.000 0.001 0.017
## iOp46   0.000 0.000 0.000 0.000     0   0.000 0.023 0.000 0.000 0.000
## iOp45   0.000 0.000 0.000 0.000     0   0.000 0.626 0.001 0.000 0.000
## iOP13.5 0.004 0.000 0.000 0.000     0   0.000 0.000 0.000 0.000 0.000
## iOp44   0.754 0.033 0.000 0.004     0   0.000 0.000 0.000 0.023 0.001
## iOp07.5 0.053 0.000 0.000 0.000     0   0.000 0.000 0.000 0.000 0.000
## iOp47   0.367 0.000 0.000 0.000     0   0.000 0.000 0.000 0.000 0.000
## iOP12.5 0.000 0.000 0.000 0.000     0   0.000 0.000 0.020 0.000 0.000
##         iOP13.5 iOp44 iOp07.5 iOp47 iOP12.5
## iOp01     0.072 1.000   0.529 1.000   0.000
## iOp03     0.000 0.368   0.000 0.000   0.000
## iOp04     0.000 0.000   0.000 0.000   0.000
## iOp10     0.000 0.075   0.000 0.000   0.000
## iOp11     0.000 0.000   0.000 0.000   0.000
## iOp02.5   0.000 0.000   0.000 0.000   0.000
## iOp42     0.000 0.000   0.000 0.000   0.000
## iOp43     0.000 0.000   0.000 0.000   0.324
## iOp46     0.000 0.324   0.000 0.000   0.000
## iOp45     0.000 0.023   0.000 0.000   0.000
## iOP13.5   0.000 0.000   0.000 0.000   0.000
## iOp44     0.000 0.000   0.000 0.000   1.000
## iOp07.5   0.000 0.000   0.000 0.000   1.000
## iOp47     0.000 0.000   0.000 0.000   1.000
## iOP12.5   0.000 0.308   0.569 0.717   0.000

5.2 Хитмап корреляций

Как видите, почти все коррелирует друг с другом, даже с учетом поправок. Такие множественные корреляции лучше всего смотреть с помощью хитмап-визуализации:

install.packages("corrplot")
library(corrplot)
corrplot(opinion_cor, method = "color", order = "hclust")

Каждый квадратик - корреляция двух колонок. Синяя - положительная корреляция, красная - отрицательная. Чем насыщеннее цвет, тем сильнее корреляция (то есть выше значение по модулю). Колонки автоматически переставляются в нужном порядке для “группировки”.

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

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

5.3 Анализ главных компонент (Principal component analysis)

Анализ главных компонент (АГК) известен как метод “уменьшения размерности”. Представьте, что вам дан набор данных с большим количеством количеством похожих переменных… Ох, надо же, наш датасет как раз именно такой!

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

Суть АГК в том, чтобы повернуть оси этого пространства так, чтобы первые оси объясняли как можно больший разброс данных, а последние - как можно меньший. Тогда мы могли бы отбросить последние оси и не очень-то многое потерять в данных.

Для двух осей это выглядит вот так:

Первая ось должна минимизировать красные расстояния. Вторая ось будет просто перпендикулярна первой оси.

Математически, АГК - это нахождение собственных векторов и собственных значений матрицы корреляций или ковариаций. Собственные вектора - это такие особенные вектора матрицы, умножив которые на данную матрицу, можно получить тот же самый вектор (т.е. того же направления), но другой длины. А вот коэффициент множителя длины нового вектора - это собственное значение. В контексте АГК, собственные вектора - это новые оси (т.е. те самые новые компоненты), а собственные значения - это размер объясняемой дисперсии с помощью новых осей.

Итак, для начала нам нужно центрировать и нормировать данные - вычесть среднее и поделить на стандартное отклонение, т.е. посчитать z-оценки. Это нужно для того, чтобы сделать все шкалы равноценными. Это особенно важно делать когда разные шкалы используют несопоставимые единицы измерения. Скажем, одна колонка - это масса человека в килограммах, а другая - рост в метрах. Если применять АГК на этих данных, то ничего хорошего не выйдет: вклад роста будет слишком маленьким. А вот если мы сделаем z-преобразование, то приведем и вес, и рост к “общему знаменателю”.

В нашем случае это не так критично, поскольку во всех случаях испытуемые отвечают по одинаковой 7-балльной шкале.

opinion_scaled <- as.data.table(scale(opinion))

В базовом R уже есть инструменты для АГК princomp() и prcomp(), считают они немного по-разному. Возьмем более рекомендуемый вариант, prcomp().

prcomp(opinion_scaled)
## Standard deviations (1, .., p=15):
##  [1] 1.7822858 1.2706276 1.0404145 1.0373031 0.9620233 0.9594423 0.9312585
##  [8] 0.9151956 0.8932286 0.8511995 0.8189853 0.8003226 0.7568713 0.7509609
## [15] 0.7274690
## 
## Rotation (n x k) = (15 x 15):
##                 PC1         PC2         PC3           PC4         PC5
## iOp01   -0.06943778  0.38155798 -0.19427723  0.4915882664  0.19803386
## iOp03   -0.20562301  0.30500547  0.47716338 -0.2907785685 -0.20900299
## iOp04    0.36177159 -0.08071718  0.34244974 -0.0003028303 -0.11323021
## iOp10   -0.34078095  0.23989272  0.19732683 -0.2139680548 -0.17104896
## iOp11    0.29931733  0.09490561  0.09826912 -0.0213681852  0.23233088
## iOp02.5  0.32443413  0.07300092  0.07894631  0.0320344221 -0.22999432
## iOp42    0.12845659  0.48677246 -0.23754435  0.0526953352 -0.14183215
## iOp43    0.16507540  0.31259574 -0.28949688 -0.4294394393 -0.28394314
## iOp46   -0.20512296  0.17552225  0.40029547  0.0300032371  0.04153754
## iOp45   -0.29856014  0.20970290 -0.31605182 -0.0187235987  0.07757389
## iOP13.5  0.35202797  0.02718279  0.05356841  0.1507532470 -0.03959969
## iOp44    0.11226061  0.20192394  0.07709088 -0.4107140003  0.80179338
## iOp07.5  0.26953643  0.30979313 -0.15452816 -0.1165836764 -0.10753154
## iOp47    0.33900566  0.15729302  0.24253529  0.1122834985  0.03911805
## iOP12.5 -0.10942316  0.33363638  0.26570013  0.4695379747  0.04614479
##                 PC6          PC7         PC8          PC9         PC10
## iOp01   -0.15062622  0.086873091 -0.36382134  0.434219525 -0.294884944
## iOp03   -0.23181854  0.028323653 -0.34924941 -0.041639577  0.099043057
## iOp04   -0.05029616  0.211504124  0.01004747  0.199516759 -0.159180605
## iOp10   -0.13095970 -0.053855368 -0.17025640 -0.051893607 -0.186371938
## iOp11    0.46069587 -0.051935276 -0.38659529  0.074794408 -0.174518794
## iOp02.5 -0.36039154 -0.451433548  0.02531913  0.005203201 -0.301394416
## iOp42   -0.19693292  0.151161377  0.15029456  0.208217043  0.586642570
## iOp43    0.18770535  0.248241667  0.31276168  0.154365239 -0.362684643
## iOp46    0.44736652 -0.352974058  0.39556974  0.473486940  0.079459941
## iOp45    0.11041482 -0.512305517 -0.04298693 -0.213770985  0.023419463
## iOP13.5 -0.26367332 -0.425241487  0.20911238 -0.010784156 -0.004230272
## iOp44   -0.28023510  0.019551048  0.17605295 -0.045507654 -0.042046820
## iOp07.5  0.30741820 -0.121070383 -0.17902370 -0.366582757 -0.039498614
## iOp47    0.17602641  0.006911587 -0.16548973 -0.174333373  0.417089629
## iOP12.5  0.04333967  0.264739933  0.39189684 -0.507508839 -0.249004492
##                 PC11        PC12         PC13        PC14        PC15
## iOp01    0.268041765 -0.10759740 -0.017454400  0.05583458 -0.07759637
## iOp03   -0.028268175 -0.14763906 -0.207863748 -0.27700923 -0.42210555
## iOp04    0.189110923 -0.09478334 -0.006973371 -0.52100969  0.55114105
## iOp10   -0.047812745  0.09035923  0.551647315  0.34238261  0.44032361
## iOp11   -0.639556895  0.07498348  0.119986008 -0.07704203 -0.04334111
## iOp02.5 -0.162737844  0.26521465 -0.464150949  0.28093279  0.11355379
## iOp42   -0.293136056  0.26303996  0.050047991 -0.12403748  0.14999167
## iOp43   -0.029759426 -0.37536418 -0.058696637  0.13243197 -0.11563024
## iOp46    0.156393341  0.14514663 -0.053539827  0.04183396 -0.04903883
## iOp45   -0.074520395 -0.37759488 -0.197166575 -0.35794150  0.35036950
## iOP13.5 -0.008700931 -0.29354808  0.586329244 -0.15432949 -0.31640366
## iOp44    0.088893908  0.05750762 -0.038890456  0.02630346  0.04428991
## iOp07.5  0.516696787  0.44286099  0.106674560 -0.14620017 -0.09660579
## iOp47    0.174231721 -0.46253063 -0.123396692  0.48427005  0.17481876
## iOP12.5 -0.177910829  0.01561185 -0.051961030 -0.05844298 -0.01982064

В принципе, z-преобразование было делать необязательно, prcomp() умеет делать это сам:

pr <- prcomp(opinion, center = TRUE, scale. = TRUE)
pr
## Standard deviations (1, .., p=15):
##  [1] 1.7822858 1.2706276 1.0404145 1.0373031 0.9620233 0.9594423 0.9312585
##  [8] 0.9151956 0.8932286 0.8511995 0.8189853 0.8003226 0.7568713 0.7509609
## [15] 0.7274690
## 
## Rotation (n x k) = (15 x 15):
##                 PC1         PC2         PC3           PC4         PC5
## iOp01   -0.06943778  0.38155798 -0.19427723  0.4915882664  0.19803386
## iOp03   -0.20562301  0.30500547  0.47716338 -0.2907785685 -0.20900299
## iOp04    0.36177159 -0.08071718  0.34244974 -0.0003028303 -0.11323021
## iOp10   -0.34078095  0.23989272  0.19732683 -0.2139680548 -0.17104896
## iOp11    0.29931733  0.09490561  0.09826912 -0.0213681852  0.23233088
## iOp02.5  0.32443413  0.07300092  0.07894631  0.0320344221 -0.22999432
## iOp42    0.12845659  0.48677246 -0.23754435  0.0526953352 -0.14183215
## iOp43    0.16507540  0.31259574 -0.28949688 -0.4294394393 -0.28394314
## iOp46   -0.20512296  0.17552225  0.40029547  0.0300032371  0.04153754
## iOp45   -0.29856014  0.20970290 -0.31605182 -0.0187235987  0.07757389
## iOP13.5  0.35202797  0.02718279  0.05356841  0.1507532470 -0.03959969
## iOp44    0.11226061  0.20192394  0.07709088 -0.4107140003  0.80179338
## iOp07.5  0.26953643  0.30979313 -0.15452816 -0.1165836764 -0.10753154
## iOp47    0.33900566  0.15729302  0.24253529  0.1122834985  0.03911805
## iOP12.5 -0.10942316  0.33363638  0.26570013  0.4695379747  0.04614479
##                 PC6          PC7         PC8          PC9         PC10
## iOp01   -0.15062622  0.086873091 -0.36382134  0.434219525 -0.294884944
## iOp03   -0.23181854  0.028323653 -0.34924941 -0.041639577  0.099043057
## iOp04   -0.05029616  0.211504124  0.01004747  0.199516759 -0.159180605
## iOp10   -0.13095970 -0.053855368 -0.17025640 -0.051893607 -0.186371938
## iOp11    0.46069587 -0.051935276 -0.38659529  0.074794408 -0.174518794
## iOp02.5 -0.36039154 -0.451433548  0.02531913  0.005203201 -0.301394416
## iOp42   -0.19693292  0.151161377  0.15029456  0.208217043  0.586642570
## iOp43    0.18770535  0.248241667  0.31276168  0.154365239 -0.362684643
## iOp46    0.44736652 -0.352974058  0.39556974  0.473486940  0.079459941
## iOp45    0.11041482 -0.512305517 -0.04298693 -0.213770985  0.023419463
## iOP13.5 -0.26367332 -0.425241487  0.20911238 -0.010784156 -0.004230272
## iOp44   -0.28023510  0.019551048  0.17605295 -0.045507654 -0.042046820
## iOp07.5  0.30741820 -0.121070383 -0.17902370 -0.366582757 -0.039498614
## iOp47    0.17602641  0.006911587 -0.16548973 -0.174333373  0.417089629
## iOP12.5  0.04333967  0.264739933  0.39189684 -0.507508839 -0.249004492
##                 PC11        PC12         PC13        PC14        PC15
## iOp01    0.268041765 -0.10759740 -0.017454400  0.05583458 -0.07759637
## iOp03   -0.028268175 -0.14763906 -0.207863748 -0.27700923 -0.42210555
## iOp04    0.189110923 -0.09478334 -0.006973371 -0.52100969  0.55114105
## iOp10   -0.047812745  0.09035923  0.551647315  0.34238261  0.44032361
## iOp11   -0.639556895  0.07498348  0.119986008 -0.07704203 -0.04334111
## iOp02.5 -0.162737844  0.26521465 -0.464150949  0.28093279  0.11355379
## iOp42   -0.293136056  0.26303996  0.050047991 -0.12403748  0.14999167
## iOp43   -0.029759426 -0.37536418 -0.058696637  0.13243197 -0.11563024
## iOp46    0.156393341  0.14514663 -0.053539827  0.04183396 -0.04903883
## iOp45   -0.074520395 -0.37759488 -0.197166575 -0.35794150  0.35036950
## iOP13.5 -0.008700931 -0.29354808  0.586329244 -0.15432949 -0.31640366
## iOp44    0.088893908  0.05750762 -0.038890456  0.02630346  0.04428991
## iOp07.5  0.516696787  0.44286099  0.106674560 -0.14620017 -0.09660579
## iOp47    0.174231721 -0.46253063 -0.123396692  0.48427005  0.17481876
## iOP12.5 -0.177910829  0.01561185 -0.051961030 -0.05844298 -0.01982064

summary() выдаст полезную информацию, в частности, долю объясненной дисперсии и кумулятивную долю объясненной дисперсии

summary(pr)
## Importance of components:
##                           PC1    PC2     PC3     PC4    PC5     PC6
## Standard deviation     1.7823 1.2706 1.04041 1.03730 0.9620 0.95944
## Proportion of Variance 0.2118 0.1076 0.07216 0.07173 0.0617 0.06137
## Cumulative Proportion  0.2118 0.3194 0.39157 0.46330 0.5250 0.58637
##                            PC7     PC8     PC9   PC10    PC11   PC12
## Standard deviation     0.93126 0.91520 0.89323 0.8512 0.81899 0.8003
## Proportion of Variance 0.05782 0.05584 0.05319 0.0483 0.04472 0.0427
## Cumulative Proportion  0.64418 0.70002 0.75321 0.8015 0.84623 0.8889
##                           PC13   PC14    PC15
## Standard deviation     0.75687 0.7510 0.72747
## Proportion of Variance 0.03819 0.0376 0.03528
## Cumulative Proportion  0.92712 0.9647 1.00000

5.3.1 Количество извлекаемых компонент

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

plot(pr)

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

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

psych::fa.parallel(opinion)

## Parallel analysis suggests that the number of factors =  6  and the number of components =  4

Можно визуализировать данные в новых осях (например, в первых двух):

plot(pr$x[,1:2])

Наконец, выбрать желаемое количество компонент можно через параметр rank. =

prcomp(opinion, scale. = TRUE, rank. = 4)
## Standard deviations (1, .., p=15):
##  [1] 1.7822858 1.2706276 1.0404145 1.0373031 0.9620233 0.9594423 0.9312585
##  [8] 0.9151956 0.8932286 0.8511995 0.8189853 0.8003226 0.7568713 0.7509609
## [15] 0.7274690
## 
## Rotation (n x k) = (15 x 4):
##                 PC1         PC2         PC3           PC4
## iOp01   -0.06943778  0.38155798 -0.19427723  0.4915882664
## iOp03   -0.20562301  0.30500547  0.47716338 -0.2907785685
## iOp04    0.36177159 -0.08071718  0.34244974 -0.0003028303
## iOp10   -0.34078095  0.23989272  0.19732683 -0.2139680548
## iOp11    0.29931733  0.09490561  0.09826912 -0.0213681852
## iOp02.5  0.32443413  0.07300092  0.07894631  0.0320344221
## iOp42    0.12845659  0.48677246 -0.23754435  0.0526953352
## iOp43    0.16507540  0.31259574 -0.28949688 -0.4294394393
## iOp46   -0.20512296  0.17552225  0.40029547  0.0300032371
## iOp45   -0.29856014  0.20970290 -0.31605182 -0.0187235987
## iOP13.5  0.35202797  0.02718279  0.05356841  0.1507532470
## iOp44    0.11226061  0.20192394  0.07709088 -0.4107140003
## iOp07.5  0.26953643  0.30979313 -0.15452816 -0.1165836764
## iOp47    0.33900566  0.15729302  0.24253529  0.1122834985
## iOP12.5 -0.10942316  0.33363638  0.26570013  0.4695379747

5.4 Эксплораторный факторный анализ

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

Чарльз Спирмен предположил, что за этими шкалами стоит некоторый единый фактор, который он назвал фактором g (от слова general) - общий интеллект. С точки зрения Спирмена, общий интеллект - это латентный фактор, который объясняет существующие корреляции между баллами за разные тесты, а то, что общий интеллект не объясняет, - это отдельные способности.

Для того, чтобы доказать свою теорию, Чарльз Спирмен придумал эксплораторный факторный анализ (ЭФА; exploratory factor analysis)6.

Суть ЭФА несколько сложнее АГК. Если в АГК мы просто вертим оси исходного пространства, чтобы максимизировать дисперсию первых осей и минимизировать дисперсию последних, то в ЭФА мы строим модель с заданным количеством латентных (т.е. “скрытых”) переменных, которые должны объяснять общую дисперсию наблюдаемых переменных и быть ортогональными (перпендикулярными) друг другу. Ну а все необъясненное остается влиянием независимых индивидуальных факторов. Кроме того, полученные латентные факторы можно еще “повращать” для большей интерпретируемости латентных факторов. Причем “вращение” может быть как ортогональным (самое распространенное - варимакс) или косоугольным (например, облимин). В первом случае факторы останутся нескоррелированными, во втором случае - нет.

Для ЭФА есть много пакетов, более того, уже знакомый нам базовый R умеет делать ЭФА.

fa_none_fit <- factanal(opinion_scaled, factors = 6, rotation = "varimax")
fa_none_fit
## 
## Call:
## factanal(x = opinion_scaled, factors = 6, rotation = "varimax")
## 
## Uniquenesses:
##   iOp01   iOp03   iOp04   iOp10   iOp11 iOp02.5   iOp42   iOp43   iOp46 
##   0.842   0.633   0.539   0.516   0.730   0.742   0.512   0.775   0.863 
##   iOp45 iOP13.5   iOp44 iOp07.5   iOp47 iOP12.5 
##   0.605   0.005   0.948   0.642   0.614   0.828 
## 
## Loadings:
##         Factor1 Factor2 Factor3 Factor4 Factor5 Factor6
## iOp01           -0.111                           0.371 
## iOp03                            0.587           0.109 
## iOp04    0.152   0.569   0.279  -0.113          -0.136 
## iOp10   -0.103  -0.270  -0.208   0.592                 
## iOp11            0.188   0.447  -0.151                 
## iOp02.5  0.279   0.246   0.247  -0.120   0.196         
## iOp42                                    0.569   0.391 
## iOp43                    0.197           0.426         
## iOp46           -0.137           0.266  -0.126   0.160 
## iOp45           -0.575  -0.102   0.168           0.150 
## iOP13.5  0.939   0.173   0.198  -0.192                 
## iOp44                    0.145           0.154         
## iOp07.5                  0.496           0.316         
## iOp47    0.148   0.313   0.482  -0.115           0.115 
## iOP12.5                          0.165           0.374 
## 
##                Factor1 Factor2 Factor3 Factor4 Factor5 Factor6
## SS loadings      1.044   0.990   0.977   0.934   0.724   0.538
## Proportion Var   0.070   0.066   0.065   0.062   0.048   0.036
## Cumulative Var   0.070   0.136   0.201   0.263   0.311   0.347
## 
## Test of the hypothesis that 6 factors are sufficient.
## The chi square statistic is 130.78 on 30 degrees of freedom.
## The p-value is 1.52e-14

Получаемый результат имеет примерно тот же вид, что и при АГК. Нужно смотреть на кумулятивную объясненную дисперсию, а также смотреть на факторные нагрузки - связь латентных факторов с наблюдаемыми шкалами. Особенно нас интересуют факторные нагрузки близкие к 1 и -1 (значения близкие к нулю скрыты). Анализируя эти шкалы, можно сделать вывод о том, что из себя представляет латентный фактор содержательно. Возьмем для примера второй фактор. В нем есть как выраженные положительные, так и отрицательные нагрузки:

  • “Consuming genetically modified (GMO) food is perfectly safe” - положительная нагрузка .569.

  • “When you are ill, how likely are you to turn to alternative medicine (such as homeopathy) rather than seeking treatment from conventional medicine?” - отрицательная нагрузка -.575.

Можно предположить, что этот фактор означает доверие к академической науке: те, у кого высокое значение по этому фактору, не боятся ГМО, а те, у кого низкое значение, - верят в эффективность гомеопатии.

Давайте визуализируем с помощью ggplot2 факторные нагрузки по первым двум факторам:

load <- fa_none_fit$loadings[,1:2]
library(ggplot2)
load_dt <- as.data.table(load)
load_dt$names <- rownames(load)
load
##             Factor1      Factor2
## iOp01   -0.01566970 -0.110771479
## iOp03   -0.07033983 -0.025900326
## iOp04    0.15240114  0.568881912
## iOp10   -0.10317583 -0.270441390
## iOp11    0.06172226  0.188263581
## iOp02.5  0.27880820  0.246291576
## iOp42    0.06696601  0.060755424
## iOp43    0.01663996  0.011630244
## iOp46   -0.05416371 -0.137096836
## iOp45   -0.06206774 -0.574575950
## iOP13.5  0.93850496  0.172697022
## iOp44    0.05999292  0.049111124
## iOp07.5  0.07140540  0.009635239
## iOp47    0.14796036  0.312706149
## iOP12.5 -0.01468160 -0.040392354
ggplot(load_dt, aes(x = Factor1, y = Factor2))+
  geom_point()+
  geom_text(aes(label = names), vjust = 1, hjust = 1)

5.5 Конфирматорный факторный анализ

Как следует из названия, если ЭФА - это более эксплораторный метод анализа, то конфирматорный факторный анализ (КФА) предназначен для проверки моделей о структуре факторов.

Как и в ЭФА, в КФА есть неизмеряемые латентные переменные, которые как-то объясняют измеряемые переменные. Однако если в ЭФА мы просто строим модель, где все латентные переменные объясняют все измеряемые переменные, то в КФА мы можем проверять более специфическую модель, где отдельные латентные переменные связаны только с определенными измеряемыми переменными. Кроме того, мы можем задавать (или не задавать) корреляции между латентными факторами и ошибками.

Самый распространенный пакет для КФА в R - lavaan (LAtent VAriable ANalysis). Впрочем, это один из самых распространенных инструментов для КФА (и структурного моделирования, о чем будет позже) вообще!

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

install.packages("lavaan")
library(lavaan)
data(HolzingerSwineford1939)

Модель предполагает, наличие трех коррелирующих друг с другом факторов: визуальный фактор (задания х1, х2, х3), текстовый фактор (задания х4, х5, х6) и фактор скорости (задания х7, х8, х9). Для того, чтобы задать такую модель, у lavaan есть свой синтаксис. Описание модели записывается в строковую переменную.

HS_model <- "
visual =~ x1 + x2 + x3
textual =~ x4 + x5 + x6
speed =~ x7 + x8 + x9
"

Затем происходит фиттинг модели с помощью функции cfa()

hs_cfa <- cfa(HS_model, data = HolzingerSwineford1939)
summary(hs_cfa)
## lavaan 0.6-4 ended normally after 35 iterations
## 
##   Optimization method                           NLMINB
##   Number of free parameters                         21
## 
##   Number of observations                           301
## 
##   Estimator                                         ML
##   Model Fit Test Statistic                      85.306
##   Degrees of freedom                                24
##   P-value (Chi-square)                           0.000
## 
## Parameter Estimates:
## 
##   Information                                 Expected
##   Information saturated (h1) model          Structured
##   Standard Errors                             Standard
## 
## Latent Variables:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   visual =~                                           
##     x1                1.000                           
##     x2                0.554    0.100    5.554    0.000
##     x3                0.729    0.109    6.685    0.000
##   textual =~                                          
##     x4                1.000                           
##     x5                1.113    0.065   17.014    0.000
##     x6                0.926    0.055   16.703    0.000
##   speed =~                                            
##     x7                1.000                           
##     x8                1.180    0.165    7.152    0.000
##     x9                1.082    0.151    7.155    0.000
## 
## Covariances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##   visual ~~                                           
##     textual           0.408    0.074    5.552    0.000
##     speed             0.262    0.056    4.660    0.000
##   textual ~~                                          
##     speed             0.173    0.049    3.518    0.000
## 
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)
##    .x1                0.549    0.114    4.833    0.000
##    .x2                1.134    0.102   11.146    0.000
##    .x3                0.844    0.091    9.317    0.000
##    .x4                0.371    0.048    7.779    0.000
##    .x5                0.446    0.058    7.642    0.000
##    .x6                0.356    0.043    8.277    0.000
##    .x7                0.799    0.081    9.823    0.000
##    .x8                0.488    0.074    6.573    0.000
##    .x9                0.566    0.071    8.003    0.000
##     visual            0.809    0.145    5.564    0.000
##     textual           0.979    0.112    8.737    0.000
##     speed             0.384    0.086    4.451    0.000

Здесь мы можем увидеть, сошлась ли модель, оценки качества модели и оценки интересующих параметров модели.

Модели для КФА принято рисовать в виде блок-схем с кружочками, квадратиками и стрелочками. Есть несколько вариантов, как именно отрисовывать модели, например, нужно ли отдельным кружочком рисовать ошибку для каждой измеряемой модели. Но, в целом, правила такие: круги - это латентные переменные, квадраты - измеряемые переменные, стрелочки - ассоциации между ними.

Для визуализации моделей, построенных в lavaan, существует пакет semPlot:

install.packages("semPlot")
library(semPlot)
semPaths(hs_cfa)

КФА лежит в основе структурного моделирования (structural equation modelling, SEM). По сути, структурное моделирование - это КФА + анализ пути (path analysis), который можно рассматривать как расширение множественной линейной регрессии - только вместо одного линейного уравнения у нас появляется целая система уровнений. Если же в системе уравнений использовать не наблюдаемые, а на латентные переменные, то получается структурное моделирование.

5.6 Другие многомерные методы

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

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

install.packages("igraph")
opinion_cor_abs <- abs(opinion_cor)
diag(opinion_cor_abs) <- 0
rownames(opinion_cor_abs) <-
colnames(opinion_cor_abs) <-
gsub("\\.", "", colnames(opinion_cor_abs))


library(igraph)
opinion_ig <-
graph.adjacency(opinion_cor_abs, weighted = TRUE, mode = "lower")
opinion_ig <- delete.edges(opinion_ig, E(opinion_ig)[weight < 0.2])
plot(
opinion_ig,
vertex.colour = "grey",
vertex.frame.color = NA,
vertex.size = strength(opinion_ig) * 3 + 2,
vertex.label.dist = 1,
edge.curved = 0,
vertex.label.color = "black"
)

В R есть множество пакетов для интерактивных визуализаций графов.

install.packages("networkD3")
library(networkD3)
opinion_nd3 <- igraph_to_networkD3(opinion_ig)
opinion_nd3$links$source <- opinion_nd3$links$source - 1
opinion_nd3$links$target <- opinion_nd3$links$target - 1
forceNetwork(
Links = opinion_nd3$links,
Nodes = opinion_nd3$nodes,
Source = 'source',
Target = 'target',
NodeID = 'name',
Group = 1,
opacity = 1
)

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