如何测试多个 CSV 中是否存在某些列名

How to test for existence of certain column names across numerous CSVs

我有大约 60 个 csv 个文件要合并。一个挑战是列的命名不一致,尽管基本上所有文件(应该)具有相同的数据。

为了处理这个问题,我想先测试哪些文件有某些列名(哪些没有)。我有一个字符串向量,其中每个元素反映一个列名,要检查它是否存在于每个 csv 文件中。

我正在尝试实现一个数据框:

例如 3 个 CSV

library(tidyverse)

df_1 <-
  tribble(~ date, ~ name, ~ age, ~ gender,
        "2020-11-29", "sarah", 43, "female")

df_2 <-
  tribble(~ createdAt, ~ person, ~ age, ~ is_female,
        "2020-10-10", "bob", 25, 0)

df_3 <- 
  tribble(~ date, ~ name, ~ age_value, ~ gender,
        "2010-01-07", "wendy", 70, "female")
write_csv(df_1, "csv_1.csv")
write_csv(df_2, "csv_2.csv")
write_csv(df_3, "csv_3.csv")

名称为

的向量

现在假设我不知道上面创建的 3 个 CSV 中的列名。 我相信每个 CSV 的列名都应该是 或者 date, name, age, age_value, gender.

col_names_to_test <-
  c(
    "date",
    "name",
    "age",
    "age_value",
    "gender"
  )

解决方案的基础

这只是我的方向,基于定义阅读和编辑功能的,然后在定义的功能上使用list.filespurrr::map_df

read_plus <- 
  function(flnm) {
  read_csv(flnm, col_types = cols(.default = "c")) # %>%
  ## here some testing against the vector `col_names_to_test` ?
  }

tbl_with_sources <-
   list.files(path = //folder-with-csv-files,
              pattern = "*.csv", 
              full.names = TRUE,
              recursive = TRUE) %>% 
  map_df(~ read_plus(.))

这只是一个大概的想法...我习惯于 tidyverse 方法,但我会对任何解决方案感到满意。

期望的输出

  filename  date  name   age age_value gender
  <chr>    <dbl> <dbl> <dbl>     <dbl>  <dbl>
1 csv_1        1     1     1         0      1
2 csv_2        0     0     1         0      0
3 csv_3        1     0     0         1      1

如果你只想要匹配col_names_to_test的列的索引,你可以使用这种方法:

library('data.table')
library('dplyr')

col_names_to_test = c('date', 'name', 'age', 'age_value', 'gender')

# define columns indexes matching the pattern
DefCols = function(input_path, patterns) {
  pattern = patterns %>%
    str_flatten('|')
  cols = input_path %>%
    fread(nrows = 1) %>%
    colnames() %>%
    str_which(pattern)
  return(cols)
}

# define the input directory
input_dir = ''

cols = input_dir %>%
  dir(pattern = '.*.csv$', full.names = TRUE, recursive = TRUE) %>%
  lapply(DefCols, col_names_to_test)

但是,如果您还想加载仅包含匹配列的数据框,您可以扩展它,如下所示:

library('data.table')
library('dplyr')

col_names_to_test = c('date', 'name', 'age', 'age_value', 'gender')

# define columns indexes matching the pattern
LoadDF = function(input_path, patterns) {
  pattern = patterns %>%
    str_flatten('|')
  cols = input_path %>%
    fread(nrows = 1) %>%
    colnames() %>%
    str_which(pattern)
  df = input_path %>%
    fread(drop = -cols) %>%
    as.data.frame()
  return(df)
}

# define the input directory
input_dir = ''

dfs = 'input_dir' %>%
  dir(pattern = '.*.csv$', full.names = TRUE, recursive = TRUE) %>%
  lapply(LoadDF, col_names_to_test)

注意:当我加载数据以检查列名时,我只保留第一行 (nrows = 1),因为我不关心每个单元格中的值。

定义一个函数 ok 给定文件名 f returns 一个命名的 0/1 向量,其长度与 col_names_to_test 相同,如果对应的分量为 1 col_names_to_test 作为列名存在于该文件中,否则为 0。然后定义一个文件名向量files。给它命名而不带扩展名,并使用 map_dfr.

对其应用 ok

这相当紧凑,只使用 purrr。

library(purrr)

ok <- function(f) +setNames(col_names_to_test %in% names(read.csv(f)), col_names_to_test)
files <- Sys.glob("csv_*.csv")
shortnames <- sub("\.csv$", "", basename(files))
files %>% setNames(shortnames) %>% map_dfr(ok, .id = "file")

给予:

# A tibble: 3 x 6
  file   date  name   age age_value gender
  <chr> <int> <int> <int>     <int>  <int>
1 csv_1     1     1     1         0      1
2 csv_2     0     0     1         0      0
3 csv_3     1     1     0         1      1

更新

已完全改版。