将自定义函数应用于 data.table 不起作用,即使该函数单独看起来没问题

Applying a custom function to a data.table doesn't work, even though the function seems okay individually

tl,dr:我的功能似乎可以工作,但后来我 lapply 它却没有。是函数还是 lapplying?

数据

我有一个数据表,其中包含已标记为字符向量的文本:

   id                   text
1:  1    c("sadness", "joy")
2:  2   c("anger", "scream")
3:  3 c("relief", "sadness")

我想用包含单词和相关情感值的字典用情感值注释我的标记化文本:

     words emotion1 emotion2
1: sadness        1        5
2:   anger        2        6
3:  relief        3        7

终极目标

我期待我的 search_function 输出类似这样的东西:

my_emotion_function(c("relief", "sadness"), lexicon_emotions)
   emotion1 emotion2
1:        2        6
my_emotion_function(c("relief", "meh"), lexicon_emotions)
   emotion1 emotion2
1:        3        7
my_emotion_function(c("meh", "ugh"), lexicon_emotions)
   emotion1 emotion2
1:       NA       NA

将此应用于标记,我将添加新列并用结果填充它们。

  id               text     emotion1 emotion2
1:  1  c("sadness", "joy")         1        5
2:  2  c("anger", "scream")        2        6
3:  3  c("relief", "sadness")      2        6

半成品的功能

该函数采用字符向量,对(键控的)情感字典进行子集匹配,并计算每个情感维度的平均分。

my_emotion_function <- function(characters, lexicon){
  return(lexicon[.(characters), lapply(.SD, mean, na.rm = TRUE), .SDcols = 2:3])
}

我不明白的地方

令我困惑和无法理解的是为什么这个函数在一个字符向量上测试时似乎运行良好(上面的例子,只在一个向量上测试它,运行良好),但是当我想lapply 它变成 data.table,它不起作用。
我不确定这个函数是在某个方面有问题还是我把它应用到data.table。我不明白为什么单个实例可以工作,但不能在 data.table

上重复

如果我执行上面的代码,在每个“文本”行中使用相同数量的标记,那么无论单词如何,每个单元格都会得到 N.A。

   id                   text emotion1 emotion2
1:  1    c("sadness", "joy")      NaN      NaN
2:  2   c("anger", "scream")      NaN      NaN
3:  3 c("relief", "sadness")      NaN      NaN

如果您使用不等数量的标记(比如第一行)对其进行测试,则每一行都包含第一行的值。

   id                   text emotion1 emotion2
1:  1                sadness        1        5
2:  2   c("anger", "scream")        1        5
3:  3 c("relief", "sadness")        1        5

我找不到原因来说明为什么我要么只得到相同的结果,要么到处都是 NA。

复制的完整代码

library(data.table)
table_of_tokens <- data.table("id" = 1:3,
                              "text" = list(c("sadness", "joy"),
                                            c("anger", "scream"),
                                            c("relief", "sadness")))
table_of_tokens[, "text" := as.character(text)]
#convert to character vector to use key-subsetting in data.table

lexicon_emotions <-
  data.table(
    "words" = c("sadness", "anger", "relief"),
    "emotion1" = 1:3,
    "emotion2" = 5:7
  )
setkey(lexicon_emotions, words)

my_emotion_function <- function(characters, lexicon) {
  return(lexicon[.(characters), 
                 lapply(.SD, mean, na.rm = TRUE), .SDcols = 2:3])
}
table_of_tokens[, c("emotion1", "emotion2") := 
                  my_emotion_function(text, lexicon_emotions)]

信用:这基本上是 syuzhet R 包的重写,它依赖于 data.frames,因此在我的情况下不够灵活或高效对于大型数据集。

编辑:这应该得到你想要的。

library(data.table)
table_of_tokens <- data.table(
    "id" = 1:3,
    "text" = list(
        c("sadness"), 
        c("anger", "scream"),
        c("relief", "grief"),
        c("relief", "grief", "sadness")
    )
)

lexicon_emotions <- data.table("words" = c("sadness", "anger", "relief"), 
                                                             "emotion1" = 1:3,
                                                             "emotion2" = 5:7,
                                                             key = "words")


emotions <- names(lexicon_emotions)[-1]
table_of_tokens[,
    (emotions) := {
        res <- lapply(text, function(x) {
            lexicon_emotions[words %chin% x,
                             lapply(.SD, mean, na.rm = TRUE),
                             .SDcols = emotions]
        })
        rbindlist(res)
    }
]

print(table_of_tokens)
> print(table_of_tokens)
   id                 text emotion1 emotion2
1:  1              sadness        1        5
2:  2         anger,scream        2        6
3:  3         relief,grief        3        7
4:  1 relief,grief,sadness        2        6

代码编写最重要的方面之一就是调试。让我们使用一个简单的 print() 调用来弄清楚函数调用期间发生了什么:

my_emotion_function <- function(characters, lexicon) {
  print(characters) ## for debugging
  return(lexicon[.(characters), 
                 lapply(.SD, mean, na.rm = TRUE), .SDcols = 2:3])
}

table_of_tokens[, c("emotion1", "emotion2") := 
                  my_emotion_function(text, lexicon_emotions)]

## [1] "c(\"sadness\", \"joy\")"    "c(\"anger\", \"scream\")"   "c(\"relief\", \"sadness\")"

这意味着我们实际上正在执行:

lexicon["c(\"sadness\", \"joy\")" ...]

## what we actually want for each token

lexicon[c("sadness", "joy"), lapply(.SD, mean, na.rm = TRUE), .SDcols = 2:3])

为此,我们想要从列表转换为字符,正如@IanCampbell 所建议的那样。另一项是我们要遍历每个元素,这意味着 lapply() 可以是我们的朋友:

table_of_tokens[, c("emotion1", "emotion2") := 
                  rbindlist(lapply(text, my_emotion_function, lexicon_emotions))]
table_of_tokens

##    id           text emotion1 emotion2
## 1:  1    sadness,joy        1        5
## 2:  2   anger,scream        2        6
## 3:  3 relief,sadness        2        6

我仍然不确定如果没有匹配会发生什么。