处理 CSV 文件中包含分号的字符变量
Dealing with character variables containing semicolons in CSV files
我有一个用分号分隔的文件,其中一个字符类型的变量在其中包含分号。 readr::read_csv2 函数将那些有分号的变量的内容拆分成更多的列,弄乱了文件的格式。
例如,当使用read_csv2打开下面的文件时,Bill的年龄栏会显示慢跑,而不是41。
文件:
name;hobbies;age
Jon;cooking;38
Bill;karate;jogging;41
Maria;fishing;32
考虑到原始文件不包含字符类型变量周围的引号,我如何导入文件以便空手道和慢跑属于爱好列?
read.csv()
您可以使用read.csv()
功能。但是会有一些警告消息(或使用 suppressWarnings()
环绕 read.csv()
函数)。如果您希望避免出现警告消息,请使用下一节中的 scan()
方法。
library(dplyr)
read.csv("./path/to/your/file.csv", sep = ";",
col.names = c("name", "hobbies", "age", "X4")) %>%
mutate(hobbies = ifelse(is.na(X4), hobbies, paste0(hobbies, ";" ,age)),
age = ifelse(is.na(X4), age, X4)) %>%
select(-X4)
扫描()文件
你可以先scan()
CSV文件先作为一个字符向量,然后用模式;
分割字符串并把它变成一个dataframe。之后,做一些 mutate()
来确定您的目标列并删除不需要的列。最后,将第一行作为列名。
library(tidyverse)
library(janitor)
semicolon_file <- scan(file = "./path/to/your/file.csv", character())
semicolon_df <- data.frame(str_split(semicolon_file, ";", simplify = T))
semicolon_df %>%
mutate(X4 = na_if(X4, ""),
X2 = ifelse(is.na(X4), X2, paste0(X2, ";" ,X3)),
X3 = ifelse(is.na(X4), X3, X4)) %>%
select(-X4) %>%
janitor::row_to_names(row_number = 1)
输出
name hobbies age
2 Jon cooking 38
3 Bill karate;jogging 41
4 Maria fishing 32
假设您的列 name
和 age
每次观察只有一个条目,而 hobbies
可能有多个条目,则以下方法有效:
- 逐行读取文件而不是将其视为 table:
tmp <- readLines(con <- file("table.csv"))
close(con)
- 找出每一行中分隔符的位置。第一个分隔符之前的条目是姓名,最后一个分隔符之后的条目是年龄:
separator_pos <- gregexpr(";", tmp)
name <- character(length(tmp) - 1)
age <- integer(length(tmp) - 1)
hobbies <- vector(length=length(tmp) - 1, "list")
- 使用 for 循环填充三个元素:
# the first line are the colnames
for(line in 2:length(tmp)){
# from the beginning of the row to the first";"
name[line-1] <- strtrim(tmp[line], separator_pos[[line]][1] -1)
# between the first ";" and the last ";".
# Every ";" is a different elemet of the list
hobbies[line-1] <- strsplit(substr(tmp[line], separator_pos[[line]][1] +1,
separator_pos[[line]][length(separator_pos[[line]])]-1),";")
#after the last ";", must be an integer
age[line-1] <- as.integer(substr(tmp[line],separator_pos[[line]][length(separator_pos[[line]])]+1,
nchar(tmp[line])))
}
- 创建一个单独的矩阵来保存爱好并按行填充它:
hobbies_matrix <- matrix(NA_character_, nrow = length(hobbies), ncol = max(lengths(hobbies)))
for(line in 1:length(hobbies))
hobbies_matrix[line,1:length(hobbies[[line]])] <- hobbies[[line]]
- 将所有变量添加到 data.frame:
df <- data.frame(name = name, hobbies = hobbies_matrix, age = age)
> df
name hobbies.1 hobbies.2 age
1 Jon cooking <NA> 38
2 Bill karate jogging 41
3 Maria fishing <NA> 32
理想情况下,您会要求生成文件的人下次正确执行 :) 但当然这并不总是可能的。
最简单的方法可能是将文件中的行读取到字符向量中,然后通过字符串匹配清理并制作数据框。
library(readr)
library(dplyr)
library(stringr)
# skip header, add it later
dataset <- read_lines("your_file.csv", skip = 1)
dataset_df <- data.frame(name = str_match(dataset, "^(.*?);")[, 2],
hobbies = str_match(dataset, ";(.*?);\d")[, 2],
age = as.numeric(str_match(dataset, ";(\d+)$")[, 2]))
结果:
name hobbies age
1 Jon cooking 38
2 Bill karate;jogging 41
3 Maria fishing 32
您还可以这样做:
read.csv(text=gsub('(^[^;]+);|;([^;]+$)', '\1,\2', readLines('file.csv')))
name hobbies age
1 Jon cooking 38
2 Bill karate;jogging 41
3 Maria fishing 32
使用最后注释中创建的文件
1) read.pattern
可以通过将模式指定为正则表达式来读取此内容,括号内的部分代表字段。
library(gsubfn)
read.pattern("hobbies.csv", pattern = '^(.*?);(.*);(.*)$', header = TRUE)
## name hobbies age
## 1 Jon cooking 38
## 2 Bill karate;jogging 41
## 3 Maria fishing 32
2) Base R 使用 base R 我们可以读入行,在中间字段周围加上引号,然后正常读入。
L <- "hobbies.csv" |>
readLines() |>
sub(pattern = ';(.*);', replacement = ';"\1";')
read.csv2(text = L)
## name hobbies age
## 1 Jon cooking 38
## 2 Bill karate;jogging 41
## 3 Maria fishing 32
备注
Lines <- "name;hobbies;age
Jon;cooking;38
Bill;karate;jogging;41
Maria;fishing;32
"
cat(Lines, file = "hobbies.csv")
我有一个用分号分隔的文件,其中一个字符类型的变量在其中包含分号。 readr::read_csv2 函数将那些有分号的变量的内容拆分成更多的列,弄乱了文件的格式。
例如,当使用read_csv2打开下面的文件时,Bill的年龄栏会显示慢跑,而不是41。
文件:
name;hobbies;age
Jon;cooking;38
Bill;karate;jogging;41
Maria;fishing;32
考虑到原始文件不包含字符类型变量周围的引号,我如何导入文件以便空手道和慢跑属于爱好列?
read.csv()
您可以使用read.csv()
功能。但是会有一些警告消息(或使用 suppressWarnings()
环绕 read.csv()
函数)。如果您希望避免出现警告消息,请使用下一节中的 scan()
方法。
library(dplyr)
read.csv("./path/to/your/file.csv", sep = ";",
col.names = c("name", "hobbies", "age", "X4")) %>%
mutate(hobbies = ifelse(is.na(X4), hobbies, paste0(hobbies, ";" ,age)),
age = ifelse(is.na(X4), age, X4)) %>%
select(-X4)
扫描()文件
你可以先scan()
CSV文件先作为一个字符向量,然后用模式;
分割字符串并把它变成一个dataframe。之后,做一些 mutate()
来确定您的目标列并删除不需要的列。最后,将第一行作为列名。
library(tidyverse)
library(janitor)
semicolon_file <- scan(file = "./path/to/your/file.csv", character())
semicolon_df <- data.frame(str_split(semicolon_file, ";", simplify = T))
semicolon_df %>%
mutate(X4 = na_if(X4, ""),
X2 = ifelse(is.na(X4), X2, paste0(X2, ";" ,X3)),
X3 = ifelse(is.na(X4), X3, X4)) %>%
select(-X4) %>%
janitor::row_to_names(row_number = 1)
输出
name hobbies age
2 Jon cooking 38
3 Bill karate;jogging 41
4 Maria fishing 32
假设您的列 name
和 age
每次观察只有一个条目,而 hobbies
可能有多个条目,则以下方法有效:
- 逐行读取文件而不是将其视为 table:
tmp <- readLines(con <- file("table.csv"))
close(con)
- 找出每一行中分隔符的位置。第一个分隔符之前的条目是姓名,最后一个分隔符之后的条目是年龄:
separator_pos <- gregexpr(";", tmp)
name <- character(length(tmp) - 1)
age <- integer(length(tmp) - 1)
hobbies <- vector(length=length(tmp) - 1, "list")
- 使用 for 循环填充三个元素:
# the first line are the colnames
for(line in 2:length(tmp)){
# from the beginning of the row to the first";"
name[line-1] <- strtrim(tmp[line], separator_pos[[line]][1] -1)
# between the first ";" and the last ";".
# Every ";" is a different elemet of the list
hobbies[line-1] <- strsplit(substr(tmp[line], separator_pos[[line]][1] +1,
separator_pos[[line]][length(separator_pos[[line]])]-1),";")
#after the last ";", must be an integer
age[line-1] <- as.integer(substr(tmp[line],separator_pos[[line]][length(separator_pos[[line]])]+1,
nchar(tmp[line])))
}
- 创建一个单独的矩阵来保存爱好并按行填充它:
hobbies_matrix <- matrix(NA_character_, nrow = length(hobbies), ncol = max(lengths(hobbies)))
for(line in 1:length(hobbies))
hobbies_matrix[line,1:length(hobbies[[line]])] <- hobbies[[line]]
- 将所有变量添加到 data.frame:
df <- data.frame(name = name, hobbies = hobbies_matrix, age = age)
> df
name hobbies.1 hobbies.2 age
1 Jon cooking <NA> 38
2 Bill karate jogging 41
3 Maria fishing <NA> 32
理想情况下,您会要求生成文件的人下次正确执行 :) 但当然这并不总是可能的。
最简单的方法可能是将文件中的行读取到字符向量中,然后通过字符串匹配清理并制作数据框。
library(readr)
library(dplyr)
library(stringr)
# skip header, add it later
dataset <- read_lines("your_file.csv", skip = 1)
dataset_df <- data.frame(name = str_match(dataset, "^(.*?);")[, 2],
hobbies = str_match(dataset, ";(.*?);\d")[, 2],
age = as.numeric(str_match(dataset, ";(\d+)$")[, 2]))
结果:
name hobbies age
1 Jon cooking 38
2 Bill karate;jogging 41
3 Maria fishing 32
您还可以这样做:
read.csv(text=gsub('(^[^;]+);|;([^;]+$)', '\1,\2', readLines('file.csv')))
name hobbies age
1 Jon cooking 38
2 Bill karate;jogging 41
3 Maria fishing 32
使用最后注释中创建的文件
1) read.pattern
可以通过将模式指定为正则表达式来读取此内容,括号内的部分代表字段。
library(gsubfn)
read.pattern("hobbies.csv", pattern = '^(.*?);(.*);(.*)$', header = TRUE)
## name hobbies age
## 1 Jon cooking 38
## 2 Bill karate;jogging 41
## 3 Maria fishing 32
2) Base R 使用 base R 我们可以读入行,在中间字段周围加上引号,然后正常读入。
L <- "hobbies.csv" |>
readLines() |>
sub(pattern = ';(.*);', replacement = ';"\1";')
read.csv2(text = L)
## name hobbies age
## 1 Jon cooking 38
## 2 Bill karate;jogging 41
## 3 Maria fishing 32
备注
Lines <- "name;hobbies;age
Jon;cooking;38
Bill;karate;jogging;41
Maria;fishing;32
"
cat(Lines, file = "hobbies.csv")