【问题标题】:Fill in word that letter is located in填写字母所在的单词
【发布时间】:2022-01-14 17:32:10
【问题描述】:

我正在处理击键数据,并且需要找到击键所在的单词。因为可能存在不可见的击键(如 Shift)或已删除的击键,所以这不是一个简单的问题,我可以迭代击键索引并定位单词。相反,我需要找到产生击键的空格分隔的单词。我确实有全文和现有文本可用,我应该能够利用它们。我尝试了使用fill()lag()cumsum() 的解决方案,但没有一个有效。

我有一个如下所示的数据框,我按experiment_id 分组:

x <- tibble(
  experiment_id = rep(c('1a','1b'),each=12),
  keystroke = rep(c('a','SPACE','SHIFT','b','e','DELETE','a','d','SPACE','m','a','n'),2),
  existing_text = rep(c('a','a ','a ','a B','a Be','a B','a Ba','a Bad','a Bad ',
                    'a Bad m','a Bad ma','a Bad man'),2),
  final_text = 'a Bad man'
)

附加列应如下所示,其中 SPACE 属于它后面的单词,而 DELETE 和删除的击键是最终单词的一部分:

within_word = c('a','a','BeDELETEad','BeDELETEad','BeDELETEad','BeDELETEad','BeDELETEad','BeDELETEad','BeDELETEad','man','man','man')

有没有办法得出这个?

编辑以获得额外帮助:在答案下方的 cmets 中,@Onyambu 提到使用 keystroke 列有一个更简单的解决方案。我发现在我更大、更复杂的数据中,existing_text 并不总是可靠的。我强烈希望主要依赖于keystroke 的解决方案。由于删除,我还添加了并发症。

【问题讨论】:

  • 您能详细说明一下吗? 在其中产生击键的空格分隔的单词是什么意思?您还可以包含一个带有 shift 键击的示例
  • 没问题。示例已更新。空格分隔的单词是被空格包围的单词。
  • 那么您在寻找什么?为什么要在第二个元素中有 NA?
  • 因为空格不在单词之内,它在单词之间。所需的结果在上面的第二个代码块中。谢谢!
  • 你不能只改变击键列吗?即用NA替换所有大写字母,然后向上填充留下1 NA

标签: r nlp tidyverse


【解决方案1】:

以下是两种方法:

第一种仅使用existing_text中的信息进行分组,并根据此分组和keystroke构造within_words列。

第二种方法只使用keystroke中的信息。


第一种方法:基于existing_text的分组和基于keystroke的内容:

我们采取三个步骤:

首先,我们根据strsplit 计算分组,其中我们查找以单词\\w 开头的空格\\s。我们需要更正"SHIFT" 的值,因为它们应该计入"SPACE" 之后的单词中。

第二步是将"SHIFT"(以及示例数据不包含的所有其他类似函数)替换为""

第三,我们用paste0(..., collapse = "")折叠字符串。

library(tidyverse)

x %>%

  # step1: construct grouping:
  mutate(word_grp = lengths(strsplit(existing_text, "(?<=\\w)\\s", perl = TRUE)) %>% 
           if_else(keystroke == "SHIFT", lead(., default = last(.)), .)) %>%
  group_by(experiment_id, word_grp) %>% 

  # step 2 & 3: first replace keys like "SHIFT" with "", the collapse with `paste0`
  mutate(within_word = str_replace_all(keystroke, c("SHIFT" = "", "SPACE" = "")) %>% 
           paste0(., collapse = ""))

#> # A tibble: 24 x 6
#> # Groups:   experiment_id, word_grp [6]
#>    experiment_id keystroke existing_text final_text word_grp within_word
#>    <chr>         <chr>     <chr>         <chr>         <int> <chr>      
#>  1 1a            a         "a"           a Bad man         1 a          
#>  2 1a            SPACE     "a "          a Bad man         1 a          
#>  3 1a            SHIFT     "a "          a Bad man         2 beDELETEad 
#>  4 1a            b         "a B"         a Bad man         2 beDELETEad 
#>  5 1a            e         "a Be"        a Bad man         2 beDELETEad 
#>  6 1a            DELETE    "a B"         a Bad man         2 beDELETEad 
#>  7 1a            a         "a Ba"        a Bad man         2 beDELETEad 
#>  8 1a            d         "a Bad"       a Bad man         2 beDELETEad 
#>  9 1a            SPACE     "a Bad "      a Bad man         2 beDELETEad 
#> 10 1a            m         "a Bad m"     a Bad man         3 man        
#> # … with 14 more rows


第二种方法:仅基于keystrokes 中的信息。

这是一种仅使用keystroke 中信息的方法。但是,如果我们只想使用 keystroke 中的数据,事情会变得更加费力。

以下是对以下步骤的简短说明:

步骤 1a:数据清理
我们需要清理keystrokes 中的数据,以便它们可以用于新列within_word。这意味着两件事:(a) 我们需要用"" 替换每个不应在within_word 中打印的击键。在此之前,我们需要 (b) 根据该键的功能更改前导击键。对于SHIFT,这意味着我们需要设置前导keystroketoupper。对于您的示例数据,这非常简单,因为我们只需要处理SHIFT。但是,在您的真实数据中可能有许多类似的其他键,例如 ALT^。所以我们需要对每个键重复步骤 1a。理想情况下,我们会想出一个函数,该函数采用键的名称和它在前导 keystroke 上使用的函数。请注意,我们尚未在此步骤中包含 "SPACE",因为我们在第 2 步中需要它。

要查看您需要在实际数据中处理多少键,我们可以过滤那些不更改 existing_textkeystrokes。在您的示例数据中,这只是SHIFT

# get all keystrokes that don't change the existing_text directly
x %>% 
  select(keystroke, existing_text) %>% 
  filter(existing_text == lag(existing_text, default = ""))

#> # A tibble: 2 x 2
#>   keystroke existing_text
#>   <chr>     <chr>        
#> 1 SHIFT     "a "         
#> 2 SHIFT     "a "

第 2 步:创建分组
我们需要在within_text 中创建单词分组。这是最复杂的一步。下面我们首先查找within_word == "SPACE" 的行以及后续行是!= "SPACE"。我们在结果上使用data.table::rleid 来获取此变量的运行长度ID。最后,我们需要为那些within_word == "SPACE" 的行减去1

第 3 步:最后一步之前的数据准备
这与步骤 1a 基本相似,我们需要将"SPACE" 替换为"",因为我们不希望它出现在我们的结果中。但是,由于我们在第 2 步中需要此列,因此我们必须在此步骤中完成数据清理。

第 4 步:折叠 within_word 中的字符串
最后,我们按experiment_idword_grp 分组,并将within_wordpaste0(..., collapse = "") 中的字符串折叠起来。

library(tidyverse)

  # step 1a: data cleaning
  mutate(within_word = if_else(lag(keystroke, default = first(keystroke)) == "SHIFT",
                               toupper(keystroke),
                               keystroke) %>%
                          str_replace_all(., c("SHIFT" = ""))) %>%  
 
  # step 1b to 1n: repeat step 1a for other keys like ALT, ^ etc. 

  # step 2: create groups
  group_by(experiment_id) %>% 
  mutate(word_grp = data.table::rleid(
      within_word == "SPACE" & lead(within_word, default = first(keystroke)) != "SPACE"
    ) %>% if_else(within_word == "SPACE", . - 1L, .)) %>% 

  # step 3: data prep before final step
  ungroup %>% 
  mutate(within_word = str_replace(within_word, "SPACE", "")) %>%
 
  # step 4: collapse
  group_by(experiment_id, word_grp) %>% 
  mutate(within_word = paste0(within_word, collapse = ""))

#> # A tibble: 24 x 6
#> # Groups:   experiment_id, word_grp [6]
#>    experiment_id keystroke existing_text final_text within_word word_grp
#>    <chr>         <chr>     <chr>         <chr>      <chr>          <int>
#>  1 1a            a         "a"           a Bad man  a                  1
#>  2 1a            SPACE     "a "          a Bad man  a                  1
#>  3 1a            SHIFT     "a "          a Bad man  BeDELETEad         3
#>  4 1a            b         "a B"         a Bad man  BeDELETEad         3
#>  5 1a            e         "a Be"        a Bad man  BeDELETEad         3
#>  6 1a            DELETE    "a B"         a Bad man  BeDELETEad         3
#>  7 1a            a         "a Ba"        a Bad man  BeDELETEad         3
#>  8 1a            d         "a Bad"       a Bad man  BeDELETEad         3
#>  9 1a            SPACE     "a Bad "      a Bad man  BeDELETEad         3
#> 10 1a            m         "a Bad m"     a Bad man  man                5
#> # … with 14 more rows

reprex package (v0.3.0) 于 2021 年 12 月 23 日创建

【讨论】:

    【解决方案2】:
    x %>%
      mutate(ww = str_remove(existing_text, fixed(lag(existing_text, default = ".")))) %>%
      group_by(grp = cumsum(ww== ' '|lag(ww == ' ', default = F))) %>%
      mutate(within_word = str_c(ww, collapse = ''),
             within_word = na_if(within_word, ' '))
    
    # A tibble: 10 x 6
    # Groups:   grp [5]
       keystroke existing_text final_text ww      grp within_word
       <chr>     <chr>         <chr>      <chr> <int> <chr>      
     1 a         "a"           a Bad man  "a"       0 a          
     2 SPACE     "a "          a Bad man  " "       1 NA         
     3 SHIFT     "a "          a Bad man  ""        2 Bad        
     4 b         "a B"         a Bad man  "B"       2 Bad        
     5 a         "a Ba"        a Bad man  "a"       2 Bad        
     6 d         "a Bad"       a Bad man  "d"       2 Bad        
     7 SPACE     "a Bad "      a Bad man  " "       3 NA         
     8 m         "a Bad m"     a Bad man  "m"       4 man        
     9 a         "a Bad ma"    a Bad man  "a"       4 man        
    10 n         "a Bad man"   a Bad man  "n"       4 man        
    

    【讨论】:

    • 您能解释一下您的代码吗?它非常密集。
    • @Adam_G 到底在哪里?在我走这条路之前,我们应该使用 Keystroke 列吗?在这个答案中,我没有使用它
    • 使用击键列做什么?我不确定我是否理解您的问题。
    • 我还应该补充一点,这个答案似乎是正确的。但是,我不明白 fixed()、按 cumsum()、单管道运算符或 str_c() 分组是如何工作的。
    • @Adam_G 在这种情况下,将 groupby 中的grp = .. 带到之前的mutate 函数,然后执行group_by(grp, .add = TRUE)
    猜你喜欢
    • 2015-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-20
    • 2014-11-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多