处理 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

假设您的列 nameage 每次观察只有一个条目,而 hobbies 可能有多个条目,则以下方法有效:

  1. 逐行读取文件而不是将其视为 table:
tmp <- readLines(con <- file("table.csv"))
close(con)
  1. 找出每一行中分隔符的位置。第一个分隔符之前的条目是姓名,最后一个分隔符之后的条目是年龄:
separator_pos <- gregexpr(";", tmp)
name <- character(length(tmp) - 1)
age <- integer(length(tmp) - 1)
hobbies <- vector(length=length(tmp) - 1, "list")
  1. 使用 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])))
} 
  1. 创建一个单独的矩阵来保存爱好并按行填充它:
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]]   

  1. 将所有变量添加到 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")