【问题标题】:R: create a new df with conditional statement (related to row n and row n-1)R:用条件语句创建一个新的df(与第n行和第n-1行有关)
【发布时间】:2021-08-31 05:49:13
【问题描述】:

我有一个数据框,其中 1 列用于参与者,1 列用于我的脑电图触发器。示例:

ID trigger
P1 SB
P1 SB
P1 resp
P1 DH
P1 Sc
P1 resp
P2 SB
P2 resp
P2 Sc
P2 SB

“resp”对应于参与者每次回答(按下按钮)的时间。如果他在Sc之后回答,则为命中,否则为miss;如果他在其他事情之后回答,那是虚惊(fa);如果他在不是 Sc 的事情之后没有回答,这是一个正确拒绝(cr)。

我想创建一个新的数据框,并为每个参与者提供 Sc 总数、命中数、未命中数、fa 数和 cr 数,如下所示:

ID Nb_Sc hit miss fa cr
P1 100 99 1 1 99
P2 50 45 5 3 47

但我完全不知道我该怎么做。有没有人有想法可以提供帮助?

感谢阅读。

【问题讨论】:

    标签: r conditional-statements


    【解决方案1】:
    library(dplyr)
    set.seed(100)
    df <- data.frame(
      ID = sample(c("P1", "P2"), 200, replace = TRUE),
      trigger = sample(c("SB", "Sc", "resp", "DH"), 200, replace = TRUE)
    )
    
    # Instead of look into previous row I look ahead based on value of current rows
    df <- df %>%
      # added group_by here for correct calculated lead / lag
      group_by(ID) %>%
      mutate(category = case_when(
        # hit for all trigger ending with c follow by a resp
        grepl(".+c$", trigger) & lead(trigger, 1) == "resp" ~ "hit",
        # miss for all trigger ending with c not follow by a resp
        grepl(".+c$", trigger) & lead(trigger, 1) != "resp" ~ "miss",
        # fa for all trigger that not ending with c and follow b a resp
        !grepl("(.+c$)|(^resp$)", trigger) & lead(trigger, 1) == "resp" ~ "fa",
        # cr for all trigger that not ending with c and not follow by a resp
        !grepl("(.+c$)|(^resp$)", trigger) & lead(trigger, 1) != "resp" ~ "cr",
        TRUE ~ "No categorized yet"
      ))
    
    # Here is what the data look like
    head(df)
    #> # A tibble: 6 x 3
    #> # Groups:   ID [2]
    #>   ID    trigger category          
    #>   <chr> <chr>   <chr>             
    #> 1 P2    Sc      miss              
    #> 2 P1    Sc      miss              
    #> 3 P2    SB      fa                
    #> 4 P2    resp    No categorized yet
    #> 5 P1    Sc      miss              
    #> 6 P1    SB      cr
    
    # all no categorized yet is for resp trigger which is accurate.
    table(df$trigger, df$category)
    #>       
    #>        cr fa hit miss No categorized yet
    #>   DH   39 14   0    0                  0
    #>   resp  0  0   0    0                 56
    #>   SB   27 18   0    0                  0
    #>   Sc    0  0  10   36                  0
    
    # now summarized the data by ID
    df %>%
      group_by(ID) %>%
      summarize(Nb_Sc = sum(trigger == "Sc"),
        hit = sum(category == "hit"),
        miss = sum(category == "miss"),
        fa = sum(category == "fa"),
        cr = sum(category == "cr"))
    #> # A tibble: 2 x 6
    #>   ID    Nb_Sc   hit  miss    fa    cr
    #>   <chr> <int> <int> <int> <int> <int>
    #> 1 P1       21     4    17    15    33
    #> 2 P2       25     6    19    17    33
    

    reprex package (v2.0.0) 于 2021-06-15 创建

    更新了正则表达式匹配的解决方案

    【讨论】:

    • 这太棒了!太感谢了!我还有一个问题:实际上,“Sc”可以是“SBc”或“L1c”或“H1c”(以“c”结尾的任何内容)。有没有办法在代码中说明这一点?
    • 我刚刚更新了解决方案以通过正则表达式而不是字符比较来匹配
    • @SinhNguyen,两个结果仍然存在差异。我什至设置了和你一样的种子?您能否至少手动检查一位参与者,然后我会相应地修改我的答案
    • 嗨,Anil,我们计算中出现差异的原因是当有resp 后面跟着另一个resp。您对此的计算将产生fa,而我的计算只是忽略了resp,因为它仅首先基于信号进行计算。在 OP 中没有说明如果一个信号后面跟着多个 resp 应该计算什么?注意到实际数据可能永远不会发生这种情况。
    【解决方案2】:

    您可以在函数中使用一个简单的 for 循环来做到这一点:

    scan.reactions = function(df){
      out.id = c()
      out.nsc = c()
      out.hit = c()
      out.miss = c()
      out.fa = c()
      out.cr = c()
      for(resp in unique(df[,1])){
        out.id = c(out.id,resp)
        seq = c('',df[df[,1]==resp,2]) ## relevant sequence of events, leading with none
        out.nsc = c(out.nsc,sum(seq=="Sc"))
        lseq = c(df[df[,1]==resp,2],'')
        t = as.matrix(table(seq=="Sc",lseq=="resp")) ## Cross table of Sc and subsequent resp
        out.hit = c(out.hit,t[2,2]) ## True/True
        out.miss = c(out.miss,t[2,1])
        out.fa = c(out.fa,t[1,2])
        out.cr = c(out.cr,t[1,1])
      }
      return(data.frame("ID"=out.id,
                        "Nb_Sc"=out.nsc,
                        "hit"=out.hit,
                        "miss"=out.miss,
                        "fa"=out.fa,
                        "cr"=out.cr))
    }
    

    我稍微更改了您的数据,以便对 P1 有一个更正确的反应并对其进行测试:

    > df = structure(list(ID = c("P1", "P1", "P1", "P1", "P1", "P1", "P1", 
    "P1", "P2", "P2", "P2", "P2"), trigger = c("SB", "SB", "resp", 
    "DH", "Sc", "resp", "Sc", "resp", "SB", "resp", "Sc", "SB")), class = "data.frame", row.names = c(NA, 
    -12L))
    
    > df
       ID trigger
    1  P1      SB
    2  P1      SB
    3  P1    resp
    4  P1      DH
    5  P1      Sc
    6  P1    resp
    7  P1      Sc
    8  P1    resp
    9  P2      SB
    10 P2    resp
    11 P2      Sc
    12 P2      SB
    
    > print(scan.reactions(df))
      ID Nb_Sc hit miss fa cr
    1 P1     2   2    0  1  6
    2 P2     1   0    1  1  3
    

    它使用Sc 和后续resp 的交叉表,似乎产生了正确的结果。

    【讨论】:

    • 为什么 P2 cr 是 3?他/她只正确错过了一个,即最后一行??
    • 有四个事件,其中只有一个是fa。所以,其他的一定是cr
    • 那么通过这个类比,为什么 P1 cr 是 6 而不是 7?
    【解决方案3】:

    我提出这个简单的解决方案。

    • 所有triggers 可以分为两部分实际触发器比如Atrigger(即!= 'resp')或ACTION 和responses 其中trigger == 'resp' REACTION
    • participants 可以在 respondAtrigger ;所以Atrigger + resp 等于每个参与者的总行数,即n()
    • 现在每个Atrigger可以分为两部分——negativespositivespositives 参与者回复和negative 他/她没有回复
    • 每个positive & negative 可以根据条件为真或假的T/F
    • Nb_Sc 只是每组 Sc 的总和
    • hit 只是 resp 前面加上 Sc 真正的积极因素的总和
    • missNb_Sc - hit(如果我没记错的话)假阴性
    • faresp 的总和,前面没有 Sc (同样,如果我是正确的,fahit 的总和应该等于每组 resp 的数量) 误报
    • cractual triggers - sum(hit + miss + fa)真正的否定
    df = structure(list(ID = c("P1", "P1", "P1", "P1", "P1", "P1", "P1", 
                               "P1", "P2", "P2", "P2", "P2"), trigger = c("SB", "SB", "resp", 
                                                                          "DH", "Sc", "resp", "Sc", "resp", "SB", "resp", "Sc", "SB")), class = "data.frame", row.names = c(NA, 
                                                                                                                                                                            -12L))
    library(dplyr)
    
    df %>%
      group_by(ID) %>%
      summarise(Nb_Sc = sum(trigger == 'Sc'),
                hit = sum(trigger == 'resp' & lag(trigger) == 'Sc'),
                miss = Nb_Sc - hit,
                fa = sum(trigger == 'resp' & lag(trigger) != 'Sc'),
                cr = n() - (sum(trigger == 'resp') + hit + miss + fa))
    
    # A tibble: 2 x 6
      ID    Nb_Sc   hit  miss    fa    cr
      <chr> <int> <int> <int> <int> <int>
    1 P1        2     2     0     1     2
    2 P2        1     0     1     1     1
    

    data by Sinh 上给出了这个答案

    # A tibble: 2 x 6
      ID    Nb_Sc   hit  miss    fa    cr
      <chr> <int> <int> <int> <int> <int>
    1 P1       21     4    17    23    25
    2 P2       25     6    19    23    27
    

    【讨论】:

    • 是的!我在计算中错过了group_by
    • 亲爱的@Anil,拜托,你能复习一下计算cr数量的公式吗?使用 Sinh 的 P1 数据,我计算了 手动 (只是为了确定)33 个 true negatives 与 Sinh 和我的计算结果一致,而您的结果仅显示 25 个。谢谢。
    • @Uwe,Sinh 已经澄清他的数据中可能有两个连续的resp,我认为情况并非如此。不过,如果您的数据中有这样的连续条目,则可以修改答案以适应这一点。并感谢您接受答案。
    • 谢谢@Anil!这很棒!我更改了(trigger == 'Sc')' by (grepl(".+c$", trigger) ==TRUE`,它在您的数据示例上效果很好(它仍然不适用于我的数据,但显然问题来自我的数据...... )。
    • @AdeLac74,使用sum((grepl(".+c$", trigger)),即不使用== TRUE
    【解决方案4】:

    这是另一种数据驱动方法,它使用二维查找表来描述两个连续类别触发器。随后,观察到的数据与查找表连接,以对事件序列进行分类和计数。

    这种方法

    • 避免硬编码 case_when() 语句或硬编码条件的总和
    • 涵盖两个连续触发器的所有可能组合,从而在类别的定义中表现出差距
    • 很容易适应类别的不同定义

    从问题和OP's comment 我了解到trigger 中的所有值都属于以下三组之一:

    1. resp = 参与者已回答
    2. Sc = 以字母 c 结尾的触发器,例如 Sc,或 SBcL1cH1c
    3. other = 所有其他既不是resp 也不是以字母c 结尾的触发器

    通过这种简化,我们可以定义连续触发器的类别,如下面的二维查找表所示:

    other Sc resp
    other cr cr fa
    Sc miss miss hit
    resp NA NA NA

    行引用前面的触发器,列引用后面的触发器。所以,other 后跟resp 属于fa 类别(误报)。

    NA表示此序列尚未分类,不应计入。 OP 没有明确指定如何在resp 之后对触发序列进行分类,所以我故意选择忽略它们以符合Sinh Nguyen's answer。但请参阅下文了解如何轻松修改分类。

    library(data.table)
    # define categories
    lut2D <- fread(
      "t1    other Sc   resp
       other cr    cr   fa
       Sc    miss  miss hit
       resp  NA    NA   NA"
    )
    # reshape to long format
    lut1D <- 
      melt(lut2D, id.var = "t1", variable.name = "t2", value.name = "category", na.rm = TRUE)[
        # order categories as expected by the OP
        , category := factor(category, levels = c("hit", "miss", "fa", "cr", "dr"))][]
    # prepare trigger values
    tmp <- as.data.table(df)[
      # re-group trigger values: all values ending with "c" 
      trigger %like% "c$", trigger := "Sc"][
        # re-group trigger values: all other values 
        !trigger %in% c("Sc", "resp"), trigger := "other"][
          # create overlapping subsequences of length 2 in wide format 
          # and summaries per ID
          , .(t1 = head(trigger, -1L), t2 = tail(trigger, -1L),
              Nb_trig = .N, Nb_Sc = sum(trigger == "Sc")), keyby = ID]
    # right join with lookup table to get matching categories, drop undefined categories
    categorised <- lut1D[tmp, on = .(t1, t2), nomatch = NULL]
    # count categories
    dcast(categorised, ID + Nb_trig + Nb_Sc ~ category, fun = length)
    

    Sinh Nguyen's data 我们得到

       ID Nb_trig Nb_Sc hit miss fa cr
    1: P1      96    21   4   17 15 33
    2: P2     104    25   6   19 17 33
    

    请注意,我为每个ID 添加了触发值计数Nb_trig。因此,对于P1,记录了 96 个trigger 值,产生 95 个长度为 2 的子序列。

    补充说明

    • 我用过data.table,因为我更熟悉它。 dplyr & tidyr 也可以。
    • 代码使用data.table的命令链。
    • 分类应用于长度为 2 的子序列。为了连接观察到的数据和查找表,两个数据集都必须重新整形,以便它们包含用于前面触发值的列 t1 和列 t2用于后续触发值。
    • 因此,重构后的查找表lut1d 如下所示
          t1    t2 category
    1: other other       cr
    2:    Sc other     miss
    3: other    Sc       cr
    4:    Sc    Sc     miss
    5: other  resp       fa
    6:    Sc  resp      hit
    
    • 和重构后的观察数据集tmp已经变成了
         ID    t1    t2 Nb_trig Nb_Sc
      1: P1    Sc    Sc      96    21
      2: P1    Sc other      96    21
      3: P1 other    Sc      96    21
      4: P1    Sc other      96    21
      5: P1 other other      96    21
     ---                             
    194: P2 other other     104    25
    195: P2 other other     104    25
    196: P2 other  resp     104    25
    197: P2  resp other     104    25
    198: P2 other  resp     104    25
    
    • 加入后,分类观察数据集categorised
            t1    t2 category ID Nb_trig Nb_Sc
      1:    Sc    Sc     miss P1      96    21
      2:    Sc other     miss P1      96    21
      3: other    Sc       cr P1      96    21
      4:    Sc other     miss P1      96    21
      5: other other       cr P1      96    21
     ---                                      
    140: other  resp       fa P2     104    25
    141: other other       cr P2     104    25
    142: other other       cr P2     104    25
    143: other  resp       fa P2     104    25
    144: other  resp       fa P2     104    25  
    
    • 请注意,未定义的类别如
      197: P2 resp other 104 25
      已从联接结果中删除且不计算在内。

    修改分类

    二维查找表很容易修改。例如,我们可以引入一个新类别dr(重复响应)来涵盖参与者连续两次或多次按下按钮的情况。

    lut2D <- fread(
      "t1    other Sc   resp
       other cr    cr   fa
       Sc    miss  miss hit
       resp  NA    NA   dr"
    )
    

    通过修改后的分类和Sinh Nguyen's data我们得到

       ID Nb_trig Nb_Sc hit miss fa cr dr
    1: P1      96    21   4   17 15 33  8
    2: P2     104    25   6   19 17 33  6
    

    这个结果可以帮助解释Sinh Nguyen's answerAnilGoyals's answer 之间的区别,正如Sinh Nguyen's comment 中所讨论的那样。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-28
      • 1970-01-01
      • 2022-06-14
      • 1970-01-01
      相关资源
      最近更新 更多