【问题标题】:Parsing a string into a nested data.table将字符串解析为嵌套的 data.table
【发布时间】:2018-11-13 03:55:17
【问题描述】:

我在一个表格中有数据,其中每一行中的一个单元格是一个多行字符串,其格式有点像末尾带有引用的文档。例如,其中一个字符串如下所示:

item A...1
item B...2
item C...3
item D...2
1=foo
2=bar
3=baz

我的最终目标是将 foo/bar/baz 提取到列中并计算匹配项。因此,对于上述内容,我最终会得到一行,包括:

foo | bar | baz
----+-----+----
1   | 2   | 1

我尝试从提取“参考”映射开始,作为嵌套的 data.table,如下所示:

code | reason
-----+-------
1    | foo
2    | bar
3    | baz

这是我尝试使用data.tablestringr 的方法。

encounter_alerts[, whys := lapply(
  str_extract_all(text, regex('^[0-9].*$', multiline = TRUE)),
  FUN = function (s) { fread(text = s, sep = '=', header = FALSE, col.names = c('code', 'reason')) }
)]

我对尝试执行此操作时收到的错误消息感到非常困惑:

Error in fread(text = s, sep = "=", header = FALSE, col.names = c("code",  :
  file not found: 1=foo

我明确使用text 而不是file,所以我不确定它是如何将文本行解释为文件名的!

当我用一行测试它时,它似乎工作正常:

> fread(text = str_extract_all(encounter_alerts[989]$text, regex('^[0-9].*$', multiline = TRUE))[[1]], sep = '=', header = FALSE, col.names = c('code', 'reason'))
   code reason
1:    1    foo
2:    2    bar

我做错了什么?有没有更好的方法来做到这一点?

谢谢!

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    注意:阅读cmets后编辑

    根据您的评论,我试图重现我理解的您的数据可能是什么样子。

    library(tidyverse)
    
    df <- tibble(
      strings = c("item A...1
    item B...2
    item C...3
    item D...2
    1=foo
    2=bar
    3=baz",
    "item A...2
    item B...2
    item C...3
    item D...1
    1=toto
    2=foo
    3=lala",
    "item A...3
    item B...3
    item C...3
    item D...1
    1=tutu
    3=ttt")
    )
    

    代码:

    get_ref <- function(string) {
      string %>%
        str_split("\n") %>%
        unlist() %>% 
        str_subset("=") %>%
        str_split_fixed("=", 2) %>%
        as_tibble() %>%
        rename(code = V1, reason = V2)
    }
    
    list1 <- map(df$strings, get_ref)
    
    get_value <- function(string) {
      string %>%
          str_split("\n") %>%
          unlist() %>% 
          str_subset("\\.\\.\\.") %>%
          str_replace_all(".*\\.\\.\\.", "") %>%
          as_tibble() %>%
        rename(code = value)
    }
    
    list2 <- map(df$strings, get_value)
    
    get_result <- function(df1, df2) {
      left_join(df1, df2) %>%
        count(reason) %>%
        spread(reason, n)
    }
    
    result <- map2_df(list1, list2, get_result)
    
    result[is.na(result)] <- 0
    
    result
    

    结果

    # A tibble: 3 x 7
        bar   baz   foo  lala  toto   ttt  tutu
      <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
    1     2     1     1     0     0     0     0
    2     0     0     2     1     1     0     0
    3     0     0     0     0     0     3     1
    

    【讨论】:

    • 很抱歉我的问题令人困惑!您在“注 2”中是正确的;我的每一行数据中都有这样一个多行字符串。我认为这种方法对我有用,但需要纠正您(可以理解!)在我的真实数据中所做的一些假设......
    • 那么,自从您谈到“行”以来,您的数据是否在数据框中?抱歉,我仍然对您的数据看起来有些困惑
    • 您介意提供更多有关其结构的信息,以便我可以帮助您调整我的代码以匹配您的数据结构吗?
    • 如果您有一个数据框,其中一个变量包含一个字符串向量,类似于您在问题中粘贴的字符串,您可以将我的代码包装在一个函数中并将其传递给pmap_df() 以应用它到每一行并输出一个数据帧,每行一个结果。如果你给我足够的关于你的数据框的信息,我会很乐意写这篇文章。
    • 我编辑了我的答案以匹配我现在理解您的数据可能看起来像的样子
    【解决方案2】:

    使用 stringr 和 dplyr 你可以很容易地做到这一点

    library(stringr)
    library(dplyr)
    v <- as.data.frame(c(  "item A...1",
             "item B...2",
            "item C...3",
             "item D...2"))
    colnames(v)<- "items"
    
    matching <- c( "1",
                   "2",
                   "3")
    Mapping <- read.table(text="code     reason
    1    foo
                          2  bar
                          3  baz
                          ", header = T)
    
    ## Answer 
    df1<- v %>%
      mutate(code = str_extract(v$items, str_c(matching, collapse = "|")))
    str(df1)
    str(Mapping)
    df1$code <- as.numeric(df1$code )
    
    df1 <- left_join(df1,Mapping)
    

    请看一下

    【讨论】:

      【解决方案3】:

      可能有更好的方法来做到这一点,但这是一个不需要任何额外库(除了你已经在使用的 stringr 之外)的解决方案。

      sample_str <- 'item A...1
      item B...2
      item C...3
      item D...2
      1=foo
      2=bar
      3=baz'
      
      lines <- stringr::str_split(sample_str, '\n', simplify = T)
      
      extracted_strs <- lines[stringr::str_detect(lines, '^\\d=\\w+$')]
      
      dfs_list <- lapply(extracted_strs, function(x) {
        str_parts <- stringr::str_split(x, '=', simplify = T)
        df_args = list()
        df_args[[str_parts[2]]] = as.integer(str_parts[1])
        df_args[['stringsAsFactors']] = F
      
        do.call(data.frame, df_args)
      })
      
      
      df <- do.call(cbind, dfs)
      

      【讨论】:

      • 谢谢。这将创建一个data.frame,其中包含foobarbar 的变量。这会比我创建的示例更容易使用吗?
      【解决方案4】:

      非常感谢 @prosoitos 提供的帮助。这是我最终使用的最终代码,高度基于公认的答案——它混合了不同的包等等,我希望最终清理它们,但最后期限会发生......

      get_code_reason_mapping <- function(alert_text) {
        alert_text %>%
          str_extract_all(regex('^[0-9]=(.*)$', multiline = T)) %>%
          unlist() %>%
          str_split_fixed("=", 2) %>%
          as.data.table() %>%
          setnames(c('code', 'reason'))
      }
      
      encounter_alerts$code_reason_mapping <- map(encounter_alerts$alert_text, get_code_reason_mapping)
      
      get_why_codes <- function(alert_text) {
        alert_text %>%
          str_extract_all(regex('[/n][0-9e][0-9>][0-9]$', multiline = TRUE)) %>%
          unlist() %>%
          str_sub(-1) %>%
          as.data.table() %>%
          setnames(c('code'))
      }
      
      encounter_alerts$why_codes <- map(encounter_alerts$alert_text, get_why_codes)
      
      get_code_counts <- function(df1, df2) {
        left_join(df1, df2) %>%
          count(reason) %>%
          spread(reason, n)
      }
      
      code_counts <- map2_df(encounter_alerts$code_reason_mapping, encounter_alerts$why_codes, get_code_counts)
      
      code_counts[is.na(code_counts)] <- 0
      
      code_counts
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-02-21
        • 2019-11-04
        • 2015-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多