首页 > 解决方案 > 在导入时在 csvs 中格式化一列

问题描述

我正在导入几个.csv,它们都是两列宽(它们是从程序中输出的)——第一列是波长,第二列是吸光度,但我用文件名命名它,以便稍后组合,如这个旧的堆栈溢出答案(将 R 中的 csv 文件组合到不同的列)。传入的 .csvs 没有标题,我知道我命名它们的方式会裁剪第一个数据点。我希望第一列没有任何小数,并将所有数字标准化为四位数字 - 我添加的代码独立工作,但不在此块中 - 我更愿意一次完成所有格式化. 我遇到了 $ 不是正确的运算符的错误,但是当我使用 [] 时,我也得到了错误。我需要这样做的列是第一列,它被命名为“波长”——这也会给我带来错误,因为波长不存在或者它是非数字的。有任何想法吗?

这是我的脚本目前的样子:

for (file in file_list) {
  f <- sub("(.*)\\.CSV", "\\1", file)
  assign(f, read.csv(file = file))
  assign(f, setNames(get(f), c(names(get(f))[0:0], "Wavelength")))
  assign(f, setNames(get(f), c(names(get(f))[1:1], file)))
  floor(f[Wavelength]) #the issues are here
  sprintf("%04d", f$Wavelength) #and here
}

数据在处理之前在 csv 中如下所示:

1       401.7664      0.1379457
2       403.8058      0.1390427
3       405.8452      0.1421666
4       407.8847      0.1463629
5       409.9241      0.1477264

我希望输出为:

     Wavelength  (file name)
1       0401      0.1379457
2       0403      0.1390427
3       0405      0.1421666
4       0407      0.1463629
5       0409      0.1477264

这是 r2evans 要求的 dput:

structure(list(X3.997270e.002 = c(401.7664, 403.8058, 405.8452, 
407.8847, 409.9241, 411.9635), X1.393858e.001 = c(0.1379457, 
0.1390427, 0.1421666, 0.1463629, 0.1477264, 0.1476971)), row.names = 
c(NA, 
6L), class = "data.frame")

提前致谢!

6/24 更新: 当我分配列名“波长”时,它只会作为字符添加,而不是作为真正的列名?当我 dput/head 文件通过后(省略 sprintf/floor 函数),它只列出文件名(第二列)。当我在 R studio 中打开 csvs 时,第一列被正确标记 - 甚至更进一步,我能够组合所有按“波长”排序的 csvs:

list_csvs <- mget(sub("(.*)\\.CSV", "\\1", file_list))
all_csvs <- Reduce(function(x, y) merge(x, y, all=T, 
      by=c("Wavelength")), list_csvs, accumulate=F)

自然地,我考虑过在此之后对列进行格式化,但是有些小数在数千位是关闭的,所以我需要在合并 csv 之前进行格式化。

我已经更新了代码以使用 read.csv 之外的 colnames:

for (file in file_list) {
  f <- sub("(.*)\\.CSV", "\\1", file)
  assign(f, read.csv(file = file, 
                     header = FALSE, 
                     row.names = NULL))
  colnames(f) <- c("Wavelength", file)
  print(summary(f))
  print(names(f))
  #floor("Wavelength")   #I'm omitting this to see the console errors
  #sprintf("%04.0f", f["Wavelength"])    #omitting this too
  }

但我收到以下错误:

attempt to set 'colnames' on an object with less than two dimensions

没有命名位,也没有 sprintf/floor 我从每个文件的摘要和名称提示中得到这个:

 Length     Class      Mode 
    1 character character 
NULL

当我尝试通过 f[1]、f[[1]]、f[,1] 或 f[[,1]] 调用第一列时,我收到有关“维数不正确”的错误消息。我可以在 R 环境中清楚地看到每个数据帧的长度为 2。我还仔细检查.row_names_info(f)了第一列没有被读取为行名。我究竟做错了什么?

标签: rcsvformatting

解决方案


我将为此建议一个 dplyr/tidyr 管道。

首先,数据设置:

writeLines(
"401.7664,0.1379457
403.8058,0.1390427
405.8452,0.1421666
407.8847,0.1463629
409.9241,0.1477264", "sample1.csv")
file.copy("sample1.csv", "sample2.csv")
file_list <- normalizePath(list.files(pattern = ".*\\.csv$", full.names = TRUE), winslash = "/")
file_list
# [1] "C:/Users/r2/StackOverflow/13765634/sample1.csv"
# [2] "C:/Users/r2/StackOverflow/13765634/sample2.csv"

首先,我将建议一种稍微不同的格式:不为文件名命名列。我喜欢这个,因为我仍将保留文件名和数据(可以说是一个类别),但它允许您将所有数据合并到一个帧中以进行更有效的处理:

library(dplyr)
library(purrr) # map*
library(tidyr) # pivot_wider
file_list %>%
  set_names(.) %>%
  # set_names(tools::file_path_sans_ext(basename(.))) %>%
  map_dfr(~ read.csv(.x, header = FALSE, col.names = c("freq", "val")),
          .id = "filename") %>%
  mutate(freq = sprintf("%04.0f", freq))
#                                          filename freq       val
# 1  C:/Users/r2/StackOverflow/13765634/sample1.csv 0402 0.1379457
# 2  C:/Users/r2/StackOverflow/13765634/sample1.csv 0404 0.1390427
# 3  C:/Users/r2/StackOverflow/13765634/sample1.csv 0406 0.1421666
# 4  C:/Users/r2/StackOverflow/13765634/sample1.csv 0408 0.1463629
# 5  C:/Users/r2/StackOverflow/13765634/sample1.csv 0410 0.1477264
# 6  C:/Users/r2/StackOverflow/13765634/sample2.csv 0402 0.1379457
# 7  C:/Users/r2/StackOverflow/13765634/sample2.csv 0404 0.1390427
# 8  C:/Users/r2/StackOverflow/13765634/sample2.csv 0406 0.1421666
# 9  C:/Users/r2/StackOverflow/13765634/sample2.csv 0408 0.1463629
# 10 C:/Users/r2/StackOverflow/13765634/sample2.csv 0410 0.1477264

选项:如果您只喜欢文件名(无路径)并且确定没有文件名冲突,请set_names(basename(.))改用。(无论如何,当使用文件名作为列名时,这一步确实是必要的。)我还将删除文件扩展名,因为它们可能全部.csv或相似。

file_list %>%
  # set_names(.) %>%
  set_names(tools::file_path_sans_ext(basename(.))) %>%
  map_dfr(~ read.csv(.x, header = FALSE, col.names = c("freq", "val")),
          .id = "filename") %>%
  mutate(freq = sprintf("%04.0f", freq))
#    filename freq       val
# 1   sample1 0402 0.1379457
# 2   sample1 0404 0.1390427
# 3   sample1 0406 0.1421666
# 4   sample1 0408 0.1463629
# 5   sample1 0410 0.1477264
# 6   sample2 0402 0.1379457
# 7   sample2 0404 0.1390427
# 8   sample2 0406 0.1421666
# 9   sample2 0408 0.1463629
# 10  sample2 0410 0.1477264

(如果您需要一次对每个数据集做某事,那么您应该使用%>% group_by(filename),不确定这是否相关。)


如果您确实需要将文件名作为值的列名,请稍微修改一下,以便将其保留为列表:

file_list %>%
  set_names(tools::file_path_sans_ext(basename(.))) %>%
  map(~ read.csv(.x, header = FALSE, col.names = c("freq", "val"))) %>%
  map2(., names(.), ~ transmute(.x, freq = sprintf("%04.0f", freq), !!.y := val))
# $sample1
#   freq   sample1
# 1 0402 0.1379457
# 2 0404 0.1390427
# 3 0406 0.1421666
# 4 0408 0.1463629
# 5 0410 0.1477264
# $sample2
#   freq   sample2
# 1 0402 0.1379457
# 2 0404 0.1390427
# 3 0406 0.1421666
# 4 0408 0.1463629
# 5 0410 0.1477264

但是我将推断最终您希望按列组合这些列,假设列中会有对齐freq。(我想不出您希望列名成为文件名的另一个原因。)

为此,试试这个,恢复到第一次使用map_dfr,引入pivot_wider

file_list %>%
  set_names(tools::file_path_sans_ext(basename(.))) %>%
  map_dfr(~ read.csv(.x, header = FALSE, col.names = c("freq", "val")),
          .id = "filename") %>%
  mutate(freq = sprintf("%04.0f", freq)) %>%
  pivot_wider(freq, names_from = filename, values_from = val)
# # A tibble: 5 x 3
#   freq  sample1 sample2
#   <chr>   <dbl>   <dbl>
# 1 0402    0.138   0.138
# 2 0404    0.139   0.139
# 3 0406    0.142   0.142
# 4 0408    0.146   0.146
# 5 0410    0.148   0.148

笔记(也许更像是一个肥皂盒):

  1. 关于您对 的使用assign,我强烈反对这种行为。由于数据的结构实际上都是相同的,我推断您将对这些文件中的每一个执行相同的操作。在这种情况下,最好使用s列表中*apply的一个函数。也就是说,它不必遍历变量名列表,而是做某事,然后……它通常更容易(编程、阅读、维护)或.data.framegetreassigndats <- lapply(dats, some_function)dats2 <- lapply(dats, function(x) { ...; x; })

  2. 关于使用文件名作为列名。一些工具(例如ggplot2)确实受益于拥有“长”数据(即,一个或多个类别列,例如filename,并且每种类型的数据都有一个列......类型与您对数据的理解有关)。您可能会从重新思考使用这些数据的想法中受益。


推荐阅读