在 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
我有一个大致遵循这种格式的数据文件:
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