В психологии для исследования когнитивного стиля диапазон эквивалентности применяются различные модификации методики «Свободная сортировка объектов» (Free Sorting Test) Гарднера. В методике обычно используется от 30 до 70 стимулов (понятий). В этой заметке я продемонстрирую некоторые возможности обработки результатов проведения методики «Свободная сортировка объектов» в R.

Симуляция данных

Итак, для начала смоделируем результаты проведения методики. Предположим, у нас было 70 стимулов (как в оригинальном варианте, предложенном Гарднером), в исследовании приняли участие 100 человек. Зададим также максимальное число групп, которое могли выделить испытуемые. Теоретически, количество групп может быть равно числу стимулов. При симуляции результатов я ограничил максимальное число групп 20.

n <- 100 # количество испытуемых
k <- 70 # количество стимулов
l <- 3 # минимульное число групп
m <- 20 # максимальное число групп
dataset <- matrix(0, ncol = k, nrow = n)
for (i in 1:n) {
    groups <- sample(l:m, size = 1) # число групп, которое выделил испытуемый
    sorting <- sample(seq_len(groups), size = k, replace = TRUE) # симмулируем сортировку бъектов
    sorting <- paste0("G", sorting) # формируем названия групп
    dataset[i, ] <- sorting # заносим строку в сводную таблицу
}
# задаём имена столбцам
colnames(dataset) <- paste0("S", 1:k)
# преобразуем матрицу в таблицу данных
dataset <- as.data.frame(dataset, stringsAsFactors = FALSE)

Для наглядности приведём часть сгенерированных данных:

head(dataset[, 1:10])
#>   S1 S2 S3 S4 S5 S6 S7 S8 S9 S10
#> 1 G3 G5 G3 G8 G1 G5 G9 G7 G3  G4
#> 2 G8 G7 G1 G2 G3 G7 G6 G2 G6  G2
#> 3 G3 G2 G1 G3 G3 G1 G2 G3 G2  G3
#> 4 G1 G1 G4 G1 G4 G1 G4 G3 G1  G1
#> 5 G7 G4 G4 G8 G8 G2 G6 G5 G7  G8
#> 6 G4 G1 G4 G6 G2 G3 G5 G1 G6  G1

Как мы видим, столбцы таблицы содержат данные по каждому из стимулов, а строки представляют собой результат группировки — в ячейках представлены условные имена групп (у каждого испытуемого они могут быть произвольные).

Обратите внимание, что результаты реального эмпирического исследования могут в значительной степени отличаться от того, что продемонстрированы в этом сообщении.

Анализ количественных показателей методики

После проведения методики обычно оценивают следующие показатели:

  • Общее количество выделенных групп;
  • Количество элементов в наибольшей по объёму группе;
  • Количество групп, содержащих один элемент.

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

Для расчёта количественных показателей методики для наших данных напишем функцию, которая будет рассчитывать нужные нам параметры:

free_sorting_summary <- function(x) {
    if (is.list(x))
        x <- unlist(x)                     # для корректной обработки строк из data.frame
    if (is.character(x))
        x <- as.factor(x)                  # tabulate работает только с числами или факторами
    # рассчитываем показатели
    freqs <- tabulate(x)                   # количество элементов в каждой группе
    res <- c(n.groups = length(freqs),     # общее число групп
             max.elements = max(freqs),    # число элементов в наибольшей группе
             n1.groups = sum(freqs == 1))  # число групп с 1 элементом
    return(res)
}

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

summary_results <- as.data.frame(t(apply(dataset, 1, free_sorting_summary)))

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

head(summary_results)
#>   n.groups max.elements n1.groups
#> 1       10           12         0
#> 2        9           13         0
#> 3        3           29         0
#> 4        4           21         0
#> 5        9           10         0
#> 6        7           13         0

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

hist(summary_results$n.groups,
     main = "Histogram the number of groups",
     xlab = "Number of groups")

Теперь рассчитаем описательные статистики по всем показателям методики, например, при помощи функции describe() из пакета psych:

library(psych)
describe(summary_results, skew = FALSE)
#>              vars   n  mean   sd median trimmed  mad min max range   se
#> n.groups        1 100 11.51 5.21     12    11.6 6.67   3  20    17 0.52
#> max.elements    2 100 12.74 6.31     10    11.6 2.97   6  32    26 0.63
#> n1.groups       3 100  0.49 0.85      0     0.3 0.00   0   4     4 0.08

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

Визуализация результатов сортировки

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

group_stimuli <- function(x) {
    if (is.list(x))
        x <- unlist(x)      # для корректной обработки строк из data.frame
    m <- outer(x, x, "==")  # сравниваем названия групп каждого стимула
    mode(m) <- "integer"    # преобразуем логичческие значения в числовые
    return(m)
}

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

group_stimuli(dataset[1, ])[1:10, 1:10]
#>     S1 S2 S3 S4 S5 S6 S7 S8 S9 S10
#> S1   1  0  1  0  0  0  0  0  1   0
#> S2   0  1  0  0  0  1  0  0  0   0
#> S3   1  0  1  0  0  0  0  0  1   0
#> S4   0  0  0  1  0  0  0  0  0   0
#> S5   0  0  0  0  1  0  0  0  0   0
#> S6   0  1  0  0  0  1  0  0  0   0
#> S7   0  0  0  0  0  0  1  0  0   0
#> S8   0  0  0  0  0  0  0  1  0   0
#> S9   1  0  1  0  0  0  0  0  1   0
#> S10  0  0  0  0  0  0  0  0  0   1

Если на пересечении столбца и строки стоит 1, то эти стимулы были объединены в одну группу, если 0, соответственно, стимулы находились в разных группах.

На основе полученной матрицы, мы можем визуализировать результаты сортировки отдельного испытуемого. Для этого воспользуемся функцией qgraph() из одноимённого пакета. Кружками на графике изображены стимулы, а линиями — связи между ними.

library(qgraph)
qgraph(group_stimuli(dataset[1,]), layout = "spring")

Функция qgraph() довольно гибко настраивается, поэтому вы можете поэкспериментировать с её параметрами для получения лучшего результата.

Иногда надо оценить, насколько пользовательская группировка совпадает с изначально предполагаемой исследователем. Предположим, что наши 70 стимулов изначально разбивались на 4 крупные группы и мы хотим узнать, как эти категории отразились в результатах испытуемого. Функция qgraph() может принимать именованный список групп, содержащих номера столбцов матрицы, которые относятся к данной группе. На графике ниже группы стимулов (категории) изображены разными цветами. Если предполагаемые исследователем категории полностью совпадают с группировкой стимулов испытуемым, то в каждой группе будут кружочки одного цвета.

qgraph(group_stimuli(dataset[1,]), layout = "spring",
       groups = list(C1 = 1:20, C2 = 21:40, C3 = 41:60, C4 = 61:70),
       color = heat.colors(4))

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

# подготавливаем матрицу для заполнения в цикле
grouping_results <- matrix(0, ncol = ncol(dataset), nrow = ncol(dataset),
                           dimnames = list(colnames(dataset), colnames(dataset)))
# суммируем результаты всех испытуемых
for (i in 1:nrow(dataset))
    grouping_results <- grouping_results + group_stimuli(dataset[i, ])
diag(grouping_results) <- 0

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

Мы можем визуализировать эту матрицу также как мы делали это с результатами одного испытуемого. При этом толщина линии будет отражать частоту объединения соответствующих стимулов в выборке.

qgraph(grouping_results, layout = "spring")

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

Для начала посмотрим сколько групп и какого объёма были выделены испытуемыми:

table(grouping_results)
#> grouping_results
#>   0   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23 
#>  70   2   8  14  50  82 178 262 432 514 604 604 624 456 358 258 166 120  52  22  20   2   2

Выполнить фильтрацию при построении графика с помощью функции qgraph() можно, указав аргумент minimum.

qgraph(grouping_results, layout = "spring", minimum = 20)

Визуализация близости стимулов

Последним штрихом в нашем обзоре будет визуализация степени близости стимулов по выборочным данным. Для решения этой задачи мы будем использовать метод многомерного шкалирования, который позволяет рассчитать координаты переменных на основе матрицы различий. Для получения матрицы различий стимулов мы будем использовать меру расстояния Говера (Gower), предназначенное для номинативных переменных. Реализация метода Говера есть в функции daisy() из пакета cluster.

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

dataset2 <- as.data.frame(t(dataset))

Теперь мы можем построить матрицу различий:

library(cluster)
dist_matrix <- daisy(dataset2, metric = "gower")

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

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

coords_stimuli <- cmdscale(dist_matrix, k = 2)

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

library(ggplot2)
labs <- rownames(coords_stimuli)
ggplot(as.data.frame(coords_stimuli),
       aes(x = V1, y = V2, label = labs)) +
    geom_point(size = 2) + # добавляем точки
    geom_text(hjust = -0.5) + # подписываем точки
    theme_bw() +
    # убираем подписи осей
    theme(axis.title = element_blank(),
          axis.text = element_blank())