如何检查大型数据帧中是否存在一组列?

How to check for existence of a set of columns in large dataframes?

提问人:humans-meet-quantities 提问时间:6/27/2023 最后编辑:jay.sfhumans-meet-quantities 更新时间:6/29/2023 访问量:70

问:

我正在从多年的调查数据中构建重复的横截面(面板)。每年的调查数据都作为单独的数据框发布,变量名称(列名)不一致,有时变量完全丢失。有 450 列,因此手动预览是不可能的。因此,我想搜索每个数据帧以检查是否存在感兴趣的变量列表。

我第一次尝试过,但这还不够,因为它按顺序遍历列名,并且只对在数据帧中找不到的第一个列名产生警告,而实际上可能存在多个列名:dplyr::select()

df <- data.frame(c1 = 1:5,
                  c2 = letters[1:5],
                  c3 = letters[1:5],
                  c4 = letters[1:5],
                  c5 = letters[1:5],
                  c6 = letters[1:5])
> 
> select(df, c(c1, c2, c7, c10, c11))
Error in `select()`:
! Can't subset columns that don't exist.
✖ Column `c7` doesn't exist.
Run `rlang::last_trace()` to see where the error occurred.

这个类似问题的答案(grep()在数据框中具有多个列名)我也发现不令人满意。

which(!is.na(match(colnames(df), c("c1", "c2", "c7", "c10", "c11"))))
[1] 1 2

返回匹配列名的位置(不是名称),但对我们来说最重要的是不匹配的列名,真正的问题有 10 个或更多感兴趣的变量,因此人性化的输出很重要。

有谁知道更好的方法?也许是一个函数,可以为每个提供的列名生成一个匹配表 (/)?TRUEFALSE

R 数据帧 匹配 GrePL

评论


答:

3赞 Maël 6/27/2023 #1

您可以使用 with 获取数据集中不存在的列的逻辑向量:%in%!

cols <- c("c1", "c2", "c7", "c10", "c11")
!cols %in% colnames(df)
#[1] FALSE FALSE  TRUE  TRUE  TRUE

或者用来获取名称:setdiff

setdiff(cols, colnames(df))
#[1] "c7"  "c10" "c11"

要直接选择 data.frame 中的列而不显示错误消息,请使用:any_of

library(dplyr)
select(df, any_of(cols))
#   c1 c2
# 1  1  a
# 2  2  b
# 3  3  c
# 4  4  d
# 5  5  e

评论

1赞 humans-meet-quantities 6/27/2023
谢谢,两者都很有用。尽管如此,使用 cols %in%,因为有 13 个变量,我必须在脑海中对 FALSE 结果和提供的列名顺序进行一些手动映射。我将它与 tibble 组合在一起,它产生了可读的输出:'tibble(cols, cols %in% colnames(df))'
1赞 humans-meet-quantities 6/27/2023
“setdiff”是我工作方式的一个简单而优雅的解决方案。(根据每年 df 中的预期列名列表查找那些缺失的变量,并尝试选择所有数据帧中存在的最大变量集)。
1赞 jay.sf 6/27/2023 #2

您可以使用一种方法。同时使用感兴趣变量的名称向量以及数据框的命名列表。outer

v <- c('X1', 'X3', 'X7', 'X10', 'X11') |> setNames(nm=_)

f <- \(x, y) x %in% names(y)
t(outer(v, mget(ls(pattern='^df\\d$')), Vectorize(f)))
#       X1    X3    X7  X10   X11
# df1 TRUE  TRUE  TRUE TRUE FALSE
# df2 TRUE FALSE  TRUE TRUE FALSE
# df3 TRUE  TRUE FALSE TRUE FALSE

代替你也可以对列表进行硬编码,使用mget(ls(.))

list(df1=df1, df2=df2, df3=df3)

解释:我们想知道该陈述是否为真,以及对于多个数据帧中的每一个。我们需要的是一种双 for 循环方法,外循环在数据帧上,内循环在 .但是,我们可以使用 ,可用于此类问题,在两个向量 X 和 Y 中,它将 Y 的每个元素迭代到 X 的每个元素上并抛出一个矩阵。我们只需要声明。v %in% names(df)vouterVectorize

编辑

您可能希望将一致的逻辑应用于一组数据框中的变量命名。例如,三个字符来缩写含义(会有所帮助),但更重要的是后跟两个数字(或任何数字,只要全局一致)具有一致的含义。这将允许您使用 .abbreviate('ethnicity', 3)endsWith

ptt <- sprintf('%02d', c(1, 3, 7, 10, 11)) |> setNames(nm=_)

注意:方便地用前导零填充最多两位数字,并且是通过管道输入的基本占位符,即输出名称与每个元素的内容相同(我们希望矩阵能够很好地显示):sprintf('%02d', .)%02d_setNames

ptt
#   01   03   07   10   11 
# "01" "03" "07" "10" "11" 

f <- \(x, y) any(endsWith(names(x), y))

适配解决方案:outer

outer(mget(ls(pattern='^df[4-6]$')), ptt, Vectorize(f))
#       01    03    07   10    11
# df4 TRUE  TRUE  TRUE TRUE FALSE
# df5 TRUE FALSE  TRUE TRUE FALSE
# df6 TRUE  TRUE FALSE TRUE FALSE

同样,搜索模式查找 df4、df5 和 df6;但不是你可以使用命名列表,而是很整洁。'^df[4-6]$'mget(.)list(df4=df4, df5=df5, df6=df6)mget


数据:

df1 <- df2 <- df3 <- data.frame(matrix(,2, 10))

df2$X3  <- NULL
df3$X2  <- df3$X7  <- NULL


df4 <- df5 <- df6 <- data.frame(matrix(,2, 10))

names(df4) <- paste0('eth', sprintf('%02d', seq_along(df4)))
names(df5) <- paste0('rgn', sprintf('%02d', seq_along(df5)))
names(df6) <- paste0('inc', sprintf('%02d', seq_along(df6)))


df5[3] <- NULL
df6[2] <- df6[7]  <- NULL

评论

0赞 humans-meet-quantities 6/27/2023
这令人印象深刻,但我太业余了,无法完全理解这里发生了什么。
0赞 jay.sf 6/27/2023
@humans-meet-quantities 添加解释,有意义吗?
0赞 humans-meet-quantities 6/27/2023
现在更有意义了,我很欣赏你的解释。这个解决方案比我的问题更进一步,将所有 df 的输出放在一起。由于数据集之间的列名略有不同(变量定义的更新),因此我正在逐个 df 处理该问题。我想更好地应用循环,所以感谢您的解释。
0赞 jay.sf 6/27/2023
@humans-meet-quant 数据帧中的名称在哪些方面有所不同?也许有一个解决方案。
0赞 humans-meet-quantities 6/29/2023
通常列名的第一个字符是相同的,就像种族一样,它是 eth01、ethn02 等,值大致相同。我已经在 df 的基础上使用 setdiff() 逐个 df 解决了这个问题的版本,但我钦佩您对完美解决方案的搜索。
0赞 jkatam 6/27/2023 #3

此外,我们可以使用下面的代码来创建向量,一旦我们知道了存在的变量,那么我们就可以在 select() 中将该向量与 all_of() 一起使用vars_invars_notinvars_in

cols <- c("c10", "c2", "c7", "c1", "c11")

vars_in <- cols[which(!is.na(match(cols,colnames(df))))]
[1] "c2" "c1"

vars_notin <- cols[which(is.na(match(cols,colnames(df))))]
[1] "c10" "c7"  "c11"

select(df, all_of(vars_in))

# output

  c2 c1
1  a  1
2  b  2
3  c  3
4  d  4
5  e  5