如何将结构松散的文本文件中的 table 读取到 R 中的数据框中?
How can I read a table in a loosely structured text file into a data frame in R?
查看 this NOAA web page 上的 "Estimated Global Trend daily values" 文件。这是一个 .txt
文件,大约有 50 header 行(用前导 #
标识),后跟几千行表格数据。下载文件的link嵌入在下面的代码中。
我如何读取此文件,以便最终得到具有适当列名和数据的数据框(或 tibble)?
我知道的所有 text-to-data 函数都被那些 header 行阻碍了。这是我刚刚试过的,模仿了 this SO Q&A。我的想法是将文件读入行列表,然后从列表中删除以 #
开头的行,然后删除其余行 do.call(rbind, ...)
。顶部的下载部分工作正常,但是当我 运行 该函数时,我得到一个空列表。
temp <- paste0(tempfile(), ".txt")
download.file("ftp://aftp.cmdl.noaa.gov/products/trends/co2/co2_trend_gl.txt",
destfile = temp, mode = "wb")
processFile = function(filepath) {
dat_list <- list()
con = file(filepath, "r")
while ( TRUE ) {
line = readLines(con, n = 1)
if ( length(line) == 0 ) {
break
}
append(dat_list, line)
}
close(con)
return(dat_list)
}
dat_list <- processFile(temp)
这是一个可能的选择
processFile = function(filepath, header=TRUE, ...) {
lines <- readLines(filepath)
comments <- which(grepl("^#", lines))
header_row <- gsub("^#","",lines[tail(comments,1)])
data <- read.table(text=c(header_row, lines[-comments]), header=header, ...)
return(data)
}
processFile(temp)
我们的想法是我们读取所有行,找到以“#”开头的行并忽略它们,除了最后一行将用作 header。我们从 header 中删除“#”(否则它通常被视为注释),然后将其传递给 read.table
以解析数据。
这里有一些绕过您的功能的选项,您可以混合搭配。
在您已经知道列名的最简单(尽管不太可能)的情况下,您可以使用 read.table
并手动输入列名。默认选项 comment.char = "#"
意味着这些注释行将被省略。
read.table(temp, col.names = c("year", "month", "day", "cycle", "trend"))
更有可能的是您不知道那些列名,但可以通过计算出有多少注释行然后只阅读最后一行来得到它们.这样您就不必阅读比您需要的更多的文件;这是一个足够小的文件,它不会产生很大的差异,但在更大的文件中它可能会。我正在通过访问命令行进行计数,只是因为这是我知道的方式。另请注意,我将文件保存在更简单的路径中;您可以改为将命令与 temp
变量粘贴在一起。
同样,默认省略注释。
n_comments <- as.numeric(system("grep '^# ' co2.txt | wc -l", intern = TRUE))
hdrs <- scan(temp, skip = n_comments - 1, nlines = 1, what = "character")[-1]
read.table(temp, col.names = hdrs)
或使用 dplyr
和 stringr
,读取所有行,分离出注释以提取列名,然后过滤以删除注释行并分离到字段中,分配列名你刚刚退出。同样,对于更大的文件,这可能会变得很麻烦。
library(dplyr)
lines <- data.frame(text = readLines(temp), stringsAsFactors = FALSE)
comments <- lines %>%
filter(stringr::str_detect(text, "^#"))
hdrs <- strsplit(comments[nrow(comments), 1], "\s+")[[1]][-1]
lines %>%
filter(!stringr::str_detect(text, "^#")) %>%
mutate(text = trimws(text)) %>%
tidyr::separate(text, into = hdrs, sep = "\s+") %>%
mutate_all(as.numeric)
查看 this NOAA web page 上的 "Estimated Global Trend daily values" 文件。这是一个 .txt
文件,大约有 50 header 行(用前导 #
标识),后跟几千行表格数据。下载文件的link嵌入在下面的代码中。
我如何读取此文件,以便最终得到具有适当列名和数据的数据框(或 tibble)?
我知道的所有 text-to-data 函数都被那些 header 行阻碍了。这是我刚刚试过的,模仿了 this SO Q&A。我的想法是将文件读入行列表,然后从列表中删除以 #
开头的行,然后删除其余行 do.call(rbind, ...)
。顶部的下载部分工作正常,但是当我 运行 该函数时,我得到一个空列表。
temp <- paste0(tempfile(), ".txt")
download.file("ftp://aftp.cmdl.noaa.gov/products/trends/co2/co2_trend_gl.txt",
destfile = temp, mode = "wb")
processFile = function(filepath) {
dat_list <- list()
con = file(filepath, "r")
while ( TRUE ) {
line = readLines(con, n = 1)
if ( length(line) == 0 ) {
break
}
append(dat_list, line)
}
close(con)
return(dat_list)
}
dat_list <- processFile(temp)
这是一个可能的选择
processFile = function(filepath, header=TRUE, ...) {
lines <- readLines(filepath)
comments <- which(grepl("^#", lines))
header_row <- gsub("^#","",lines[tail(comments,1)])
data <- read.table(text=c(header_row, lines[-comments]), header=header, ...)
return(data)
}
processFile(temp)
我们的想法是我们读取所有行,找到以“#”开头的行并忽略它们,除了最后一行将用作 header。我们从 header 中删除“#”(否则它通常被视为注释),然后将其传递给 read.table
以解析数据。
这里有一些绕过您的功能的选项,您可以混合搭配。
在您已经知道列名的最简单(尽管不太可能)的情况下,您可以使用 read.table
并手动输入列名。默认选项 comment.char = "#"
意味着这些注释行将被省略。
read.table(temp, col.names = c("year", "month", "day", "cycle", "trend"))
更有可能的是您不知道那些列名,但可以通过计算出有多少注释行然后只阅读最后一行来得到它们.这样您就不必阅读比您需要的更多的文件;这是一个足够小的文件,它不会产生很大的差异,但在更大的文件中它可能会。我正在通过访问命令行进行计数,只是因为这是我知道的方式。另请注意,我将文件保存在更简单的路径中;您可以改为将命令与 temp
变量粘贴在一起。
同样,默认省略注释。
n_comments <- as.numeric(system("grep '^# ' co2.txt | wc -l", intern = TRUE))
hdrs <- scan(temp, skip = n_comments - 1, nlines = 1, what = "character")[-1]
read.table(temp, col.names = hdrs)
或使用 dplyr
和 stringr
,读取所有行,分离出注释以提取列名,然后过滤以删除注释行并分离到字段中,分配列名你刚刚退出。同样,对于更大的文件,这可能会变得很麻烦。
library(dplyr)
lines <- data.frame(text = readLines(temp), stringsAsFactors = FALSE)
comments <- lines %>%
filter(stringr::str_detect(text, "^#"))
hdrs <- strsplit(comments[nrow(comments), 1], "\s+")[[1]][-1]
lines %>%
filter(!stringr::str_detect(text, "^#")) %>%
mutate(text = trimws(text)) %>%
tidyr::separate(text, into = hdrs, sep = "\s+") %>%
mutate_all(as.numeric)