Применить (косинусную) меру сходства к таблице данных

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

Здесь ниже добавлена ​​выдержка из данных, а в конце вопроса в dput добавлен пример немного большего размера.

pnum invid dom_st prim_st pat_st net_st
 1: 7265873 24104      0       1      1      0
 2: 7266757 38775      1       2      2      3
 3: 7266757 38776      1       2      2      3
 4: 7268524 34281      1       3      2      2
 5: 7268524 34282      1       3      2      2
 6: 7272620 20002      0       1      2      0
 7: 7272620 22284      0       1      2      0
 8: 7273253 31921      1       1      1      4
 9: 7273253 31922      1       1      1      4
10: 7283628 26841      1       1      1      2
11: 7283628 26843      1       1      1      2
12: 7289442 17763      2      11     48     10
13: 7289442 17764      2      11     63      9
14: 7289525 38087      0       1      1      0
15: 7289525 38088      0       2      1      0
16: 7289525 38089      0       3      1      1

Цель состоит в том, чтобы создать меру сходства для каждого «pnum», которая сравнивает значения четырех последних столбцов во всех «invid». Количество «инвидов» на «пнум» варьируется от 2 до 26.

РЕДАКТИРОВАТЬ 1: Конкретно, для «pnum» 7266757 (строки 2 и 3) я хочу сходства между вектором th для invid 38775 (1,2,2,3) и invid 38776 (1,2,2,3), так что этот должен дать результат 1. Для 'pnum' 7289525 (строки 14-16) мне нужно сходство между тремя векторами-строками (0,1,1,0), (0,2,1,0) и (0,3,1,1). Это дает следующее:

simil(matrix(c(0,1,1,0,0,2,1,0,0,3,1,1), nrow = 3, byrow = TRUE), method = "cosine")
          1         2
2 0.9486833          
3 0.8528029 0.9438798

На последнем этапе (может быть отдельная формула) я хотел бы «уменьшить» эту матрицу (для команд из n> 2) до одного значения, которое в идеале должно быть ограничено между 0 и 1. Простой способ сделать это будет просто взять среднее значение результата матрицы, но, возможно, есть более разумный способ?

Я попробовал следующее (с данными, хранящимися в data.table 'dt', но это дало следующую ошибку:

library('proxy')    
sim <- dt[, simil(dt, method="cosine"), by = pnum]
    Error in .Call("R_cosine", c(4262069, 4262069, 4262069, 4273567, 4273567, : negative length vectors are not allowed

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

Общий набор данных составляет около 150 000 строк с примерно 92 000 проектов «pnum».

structure(list(pnum = c(7265873, 7266757, 7266757, 7268524, 7268524, 
7272620, 7272620, 7273253, 7273253, 7283628, 7283628, 7289442, 
7289442, 7289525, 7289525, 7289525, 7301987, 7301987, 7305259, 
7305259, 7307986, 7307986, 7310332, 7310332, 7333490, 7333490, 
7333502, 7333502, 7414991, 7414991), invid = c(24104, 38775, 
38776, 34281, 34282, 20002, 22284, 31921, 31922, 26841, 26843, 
17763, 17764, 38087, 38088, 38089, 34843, 38412, 32514, 33946, 
28587, 28588, 17204, 17205, 28587, 28588, 28587, 28588, 37008, 
37009), dom_st = c(0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 2, 2, 0, 
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0), prim_st = c(1, 
2, 2, 3, 3, 1, 1, 1, 1, 1, 1, 11, 11, 1, 2, 3, 3, 3, 1, 1, 5, 
5, 3, 3, 5, 5, 5, 5, 3, 3), pat_st = c(1, 2, 2, 2, 2, 2, 2, 1, 
1, 1, 1, 48, 63, 1, 1, 1, 1, 1, 1, 1, 5, 5, 14, 14, 5, 5, 5, 
5, 1, 1), net_st = c(0, 3, 3, 2, 2, 0, 0, 4, 4, 2, 2, 10, 9, 
0, 0, 1, 2, 2, 0, 0, 2, 2, 4, 4, 2, 2, 2, 2, 0, 0)), .Names = c("pnum", 
"invid", "dom_st", "prim_st", "pat_st", "net_st"), class = c("data.table", 
"data.frame"), row.names = c(NA, -30L), .internal.selfref = <pointer: 0x0000000000230788>)

person SJDS    schedule 03.05.2017    source источник
comment
Непонятно, что вы пытаетесь сделать. simil(dt, method="cosine"), если это допустимый код, будет оценивать один и тот же результат в каждой группе by=, поскольку вы на самом деле не используете подмножество данных, связанных с ним...   -  person Frank    schedule 03.05.2017
comment
Не уверен, что полностью понимаю ваш комментарий @Frank. Я надеюсь определить матрицу сходства косинусов для каждого проекта «pnum» между разными участниками проекта (столбец «invid»). Применение кода с использованием 'by = pnum' из пакета 'data.table' слишком упрощенно (поэтому и не работает). Я добавляю спецификацию в вопрос, чтобы сделать цель более ясной. Спасибо   -  person SJDS    schedule 03.05.2017


Ответы (1)


Это работает для меня:

library(data.table)
setDT(DT)

# find relevant columns for call to simil
cols <- stringr::str_subset(names(DT), "_st$")
cols
#[1] "dom_st"  "prim_st" "pat_st"  "net_st"

DT[, (mean(proxy::simil(.SD, method="cosine"))), .SDcols = cols, by = pnum]
#       pnum        V1
# 1: 7265873       NaN
# 2: 7266757 1.0000000
# 3: 7268524 1.0000000
# 4: 7272620 1.0000000
# 5: 7273253 1.0000000
# 6: 7283628 1.0000000
# 7: 7289442 0.9968006
# 8: 7289525 0.9151220
# 9: 7301987 1.0000000
#10: 7305259 1.0000000
#11: 7307986 1.0000000
#12: 7310332 1.0000000
#13: 7333490 1.0000000
#14: 7333502 1.0000000
#15: 7414991 1.0000000

Примечание. Мне нужно заключить выражение j в круглые скобки. Без этого я получаю сообщения об ошибках, которые я не понимаю:

DT[, mean(proxy::simil(.SD, method="cosine")), .SDcols = cols, by = pnum]

Ошибка в FUN(X[[i]], ...) :
Недопустимый столбец: он имеет размеры. Не могу отформатировать. Если это результат data.table(table()), вместо этого используйте as.data.table(table()).

Изменить 1

Если вы хотите получить матрицы сходства для каждого pnum (до их усреднения), я предлагаю использовать lapply(), который возвращает список:

pnums <- DT[, unique(pnum)]
results <- lapply(pnums, function(x) {
  proxy::simil(DT[pnum == x, cols, with = FALSE], method="cosine")
})
setNames(results, pnums)
#$`7265873`
#simil(0)
#
#$`7266757`
#  1
#2 1
#
#$`7268524`
#  1
#2 1
#
#$`7272620`
#  1
#2 1
#
#$`7273253`
#  1
#2 1
#
#$`7283628`
#  1
#2 1
#
#$`7289442`
#          1
#2 0.9968006
#
#$`7289525`
#          1         2
#2 0.9486833          
#3 0.8528029 0.9438798
#
#$`7301987`
#  1
#2 1
#
#$`7305259`
#  1
#2 1
#
#$`7307986`
#  1
#2 1
#
#$`7310332`
#  1
#2 1
#
#$`7333490`
#  1
#2 1
#
#$`7333502`
#  1
#2 1
#
#$`7414991`
#  1
#2 1

Редактировать 2

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

DT[, {
  sim_mat <- proxy::simil(.SD, method="cosine")
  list(min = min(sim_mat), max = max(sim_mat), 
       mean = mean(sim_mat), sd = sd(sim_mat))
}, .SDcols = cols, by = pnum]
#       pnum       min       max      mean         sd
# 1: 7265873       Inf      -Inf       NaN         NA
# 2: 7266757 1.0000000 1.0000000 1.0000000         NA
# 3: 7268524 1.0000000 1.0000000 1.0000000         NA
# 4: 7272620 1.0000000 1.0000000 1.0000000         NA
# 5: 7273253 1.0000000 1.0000000 1.0000000         NA
# 6: 7283628 1.0000000 1.0000000 1.0000000         NA
# 7: 7289442 0.9968006 0.9968006 0.9968006         NA
# 8: 7289525 0.8528029 0.9486833 0.9151220 0.05402336
# 9: 7301987 1.0000000 1.0000000 1.0000000         NA
#10: 7305259 1.0000000 1.0000000 1.0000000         NA
#11: 7307986 1.0000000 1.0000000 1.0000000         NA
#12: 7310332 1.0000000 1.0000000 1.0000000         NA
#13: 7333490 1.0000000 1.0000000 1.0000000         NA
#14: 7333502 1.0000000 1.0000000 1.0000000         NA
#15: 7414991 1.0000000 1.0000000 1.0000000         NA

Данные

DT <- structure(list(pnum = c(7265873, 7266757, 7266757, 7268524, 7268524, 
7272620, 7272620, 7273253, 7273253, 7283628, 7283628, 7289442, 
7289442, 7289525, 7289525, 7289525, 7301987, 7301987, 7305259, 
7305259, 7307986, 7307986, 7310332, 7310332, 7333490, 7333490, 
7333502, 7333502, 7414991, 7414991), invid = c(24104, 38775, 
38776, 34281, 34282, 20002, 22284, 31921, 31922, 26841, 26843, 
17763, 17764, 38087, 38088, 38089, 34843, 38412, 32514, 33946, 
28587, 28588, 17204, 17205, 28587, 28588, 28587, 28588, 37008, 
37009), dom_st = c(0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 2, 2, 0, 
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0), prim_st = c(1, 
2, 2, 3, 3, 1, 1, 1, 1, 1, 1, 11, 11, 1, 2, 3, 3, 3, 1, 1, 5, 
5, 3, 3, 5, 5, 5, 5, 3, 3), pat_st = c(1, 2, 2, 2, 2, 2, 2, 1, 
1, 1, 1, 48, 63, 1, 1, 1, 1, 1, 1, 1, 5, 5, 14, 14, 5, 5, 5, 
5, 1, 1), net_st = c(0, 3, 3, 2, 2, 0, 0, 4, 4, 2, 2, 10, 9, 
0, 0, 1, 2, 2, 0, 0, 2, 2, 4, 4, 2, 2, 2, 2, 0, 0)), .Names = c("pnum", 
"invid", "dom_st", "prim_st", "pat_st", "net_st"), class = c("data.table", 
"data.frame"), row.names = c(NA, -30L))
person Uwe    schedule 03.05.2017
comment
Привет Уве, спасибо за предложенное решение. Я вижу, вы пытаетесь сразу получить среднее значение различных индексов подобия косинуса, что могло бы сработать (хотя я не уверен, что среднее значение составляет большинство предложений, чтобы определить, насколько разные люди), но принимая среднее значение значений косинуса «pnum» 7289525 (изображено в OP, редактирование 1) дает гораздо более низкое значение (0,545163), чем то, что вы найдете (0,9151220). Значит что-то не так... - person SJDS; 03.05.2017
comment
matrix() использует параметр byrow = FALSE по умолчанию, но вы задали данные построчно, не устанавливая byrow = TRUE. mean(proxy::simil(matrix(c(0,1,1,0,0,2,1,0,0,3,1,1), nrow = 3, byrow = TRUE), method = "cosine")) returns 0.915122 like the data.table` версия. ` - person Uwe; 03.05.2017
comment
Ого, мне бы понадобилось 100 лет, чтобы понять это! благодарю за разъяснение. Огромное спасибо. Предлагаемое вами решение списка может действительно работать, но, конечно, не упрощает обработку данных позже. Я думаю, что буду использовать min, mean, max и sd вместе, чтобы получить пару значений, с которыми я могу работать. Спасибо! - person SJDS; 03.05.2017