提问人:humans-meet-quantities 提问时间:6/27/2023 最后编辑:jay.sfhumans-meet-quantities 更新时间:6/29/2023 访问量:70
如何检查大型数据帧中是否存在一组列?
How to check for existence of a set of columns in large dataframes?
问:
我正在从多年的调查数据中构建重复的横截面(面板)。每年的调查数据都作为单独的数据框发布,变量名称(列名)不一致,有时变量完全丢失。有 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 个或更多感兴趣的变量,因此人性化的输出很重要。
有谁知道更好的方法?也许是一个函数,可以为每个提供的列名生成一个匹配表 (/)?TRUE
FALSE
答:
您可以使用 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
评论
您可以使用一种方法。同时使用感兴趣变量的名称向量以及数据框的命名列表。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)
v
outer
Vectorize
编辑
您可能希望将一致的逻辑应用于一组数据框中的变量命名。例如,三个字符来缩写含义(会有所帮助),但更重要的是后跟两个数字(或任何数字,只要全局一致)具有一致的含义。这将允许您使用 .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
评论
此外,我们可以使用下面的代码来创建向量,一旦我们知道了存在的变量,那么我们就可以在 select() 中将该向量与 all_of() 一起使用vars_in
vars_notin
vars_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
评论