在 R 中避免 for 循环的聪明方法

Clever way to avoid for loop in R

我有一个大致遵循这种格式的数据文件:

HEADER:001,v1,v2,v3...,v10
v1,v2,v3,STATUS,v5...v6
.
.
.
HEADER:006,v1,v2,v3...v10
HEADER:012,v1,v2,v3...v10
v1,v2,v3,STATUS,v5...v6
v1,v2,v3,STATUS,v5...v6
.
.
.
etc

其中每个数据块或数据块都以逗号分隔的行开头,其中包括 header 和唯一(不一定是连续的)数字,然后 可能 0 或更多行,由块的 body 中的 STATUS 关键字标识。

我正在使用 readLines 读取此块,然后将其拆分为 header 行和状态行,以 CSV 格式分别读取,因为它们具有不同数量的变量:

datablocks <- readLines(filename, skipNul = T)

headers <- datablocks[grepl("HEADER", datablocks, useBytes = T)]
headers <- read.csv(text=headers, header= F, stringsAsFactors = F)

statuses <- datablocks[grepl("STATUS", datablocks, useBytes = T)]
statuses <- read.csv(text=statuses, header= F, stringsAsFactors = F)

最后,我想对这些数据进行内部连接,以便 header 中的变量包含在每个状态行中:

 all <- headers %>% inner_join(statuses, by = c("ID" = "ID"))

但是我需要一种方法来将 header 的唯一 ID 添加到它下面的每个状态行,直到下一个 header。我能想到的唯一方法是使用在初始全文数据块上运行的 for 循环:

header_id <- NA
for(i in seq(1:length(datablocks))) {
  is_header_line <- str_extract(datablocks[i], "HEADER:([^,]*)")
  if(!is.na(is_header_line)) {
    header_id <- is_header_line
  }
  datablocks[i] <- paste(datablocks[i], header_id, sep=",")
}

这很好用,但是很丑,而且不是很...R-ish。我想不出一种方法来向量化此操作,因为它需要保留一个外部变量。

我是不是漏掉了什么明显的东西?

编辑

如果输入看起来像这样

HEADER:001,a0,b0,c0,d0
e0,f0,g0,STATUS,h0,i0,j0,k0,l0,m0
HEADER:006,a1,b1,c1,d1
HEADER:012,a2,b2,c2,d2
e1,f1,g1,STATUS,h1,i1,j1,k1,l1,m1
e2,f2,g2,STATUS,h2,i2,j2,k2,l2,m2

输出应如下所示:

e0,f0,g0,h0,i0,j0,k0,l0,m0,a0,b0,c0,d0,001
e1,f1,g1,h1,i1,j1,k1,l1,m1,a2,b2,c2,d2,012
e2,f2,g2,h2,i2,j2,k2,l2,m2,a2,b2,c2,d2,012

因此需要有一个列从 parent (HEADER) 传播到 children (STATUS) 以进行内部连接。

编辑: 感谢您的澄清。特定的输入和输出使得避免误解变得非常容易。

这里我用tidyr::separate从“a0,b0,c0,d0”部分中分离出header标签,用tidyr::fill传播header信息进入以下状态行。

library(tidyverse)
read_table(col_names = "text",
         "HEADER:001,a0,b0,c0,d0
         e0,f0,g0,STATUS,h0,i0,j0,k0,l0,m0
         HEADER:006,a1,b1,c1,d1
         HEADER:012,a2,b2,c2,d2
         e1,f1,g1,STATUS,h1,i1,j1,k1,l1,m1
         e2,f2,g2,STATUS,h2,i2,j2,k2,l2,m2") %>%

mutate(status_row = str_detect(text, "STATUS"),
       header_row = str_detect(text, "HEADER"),
       header = if_else(header_row, str_remove(text, "HEADER:"), NA_character_)) %>%
  separate(header, c("header", "stub"), sep = ",", extra = "merge") %>%
  fill(header, stub) %>%
  filter(status_row) %>%
  mutate(output = paste(str_remove(text, "STATUS,"), stub, header, sep = ",")) %>%
  select(output)

结果

# A tibble: 3 x 1
  output                                    
  <chr>                                     
1 e0,f0,g0,h0,i0,j0,k0,l0,m0,a0,b0,c0,d0,001
2 e1,f1,g1,h1,i1,j1,k1,l1,m1,a2,b2,c2,d2,012
3 e2,f2,g2,h2,i2,j2,k2,l2,m2,a2,b2,c2,d2,012