如何在众多 CSV 中测试某些列名的存在
Posted
技术标签:
【中文标题】如何在众多 CSV 中测试某些列名的存在【英文标题】:How to test for existence of certain column names across numerous CSVs 【发布时间】:2021-03-11 14:39:35 【问题描述】:我有大约 60 个 csv
文件要合并。一个挑战是列的命名不一致,尽管基本上所有文件(应该)都有相同的数据。
为了解决这个问题,我想先测试一下哪些文件有某些 列名(哪些没有)。我有一个字符串向量,其中每个元素反映一个列名,以检查它是否存在于每个 csv 文件中。
我正在尝试实现一个数据框:
列:每一列对应一个列名,我要测试是否存在 行:每一行对应一个csv文件 值:在每个单元格中,0
或 1
标记 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"
)
解决方案的基础
这只是我的方向,基于定义阅读和编辑功能的this nice solution,然后在定义的功能上使用list.files
和purrr::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
【问题讨论】:
【参考方案1】:定义一个函数ok
,给定文件名f
返回一个命名的0/1向量,其长度与col_names_to_test
相同,如果col_names_to_test
的对应组件作为该文件中的列名存在,则该向量为1否则为 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
更新
已完全修改。
【讨论】:
太棒了。我将在我的情况下添加,我将files
分配给files <- list.files(path = //folder-with-csv-files, pattern = "*.csv", full.names = TRUE, recursive = TRUE)
。
另外,您介意在setNames
之前解释+
在函数ok
中的作用吗?
将逻辑向量转换为 0/1 向量。试试+ c(TRUE, FALSE)
还在处理完整路径名的解决方案中添加了短名称行。
我还有一个问题,我想知道是否需要单独的帖子或可以在此处添加 - 调整 ok
函数(或创建 ok_2
以使其返回)是否足够简单1
也用于部分匹配?例如,如果col_names_to_test
具有age
,而csv_3
具有age_value
,我们将认为这是一个匹配项,因为age_value
包含age
作为子字符串。这在我们不知道 CSV 中列名的确切措辞的情况下很有用,但可以猜测“年龄”列可能至少有 age
作为子字符串。【参考方案2】:
如果你只想要匹配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
),因为我不关心每个单元格中的值。
【讨论】:
以上是关于如何在众多 CSV 中测试某些列名的存在的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Excel 中组合具有不同列名和列顺序的多个 CSV 文件?