从数据框中提取时,使用NA填充缺失的列

3

我有一个函数,输入是一个带有某些列的数据框。

columns =['a', 'b',...,'z']

现在我有一个dataframe DF,只包含这几列 DF_columns = ['f', 'u', 'z']

如何创建一个dataframe,如果某些列不在DF中,则该dataframe的所有列都具有值NA,并且在列['f', 'u', 'z']上与DF重合?

示例:

d = data.frame('g'=c(1,2,3), 's' = c(4,2,3))
columns = letters[1:21]
columns
 [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t"
[21] "u"

> d
  g s
1 1 4
2 2 2
3 3 3
> 

在R中,您不使用[括号来创建向量。 - Thomas
我只是在写伪代码。希望这不会引起误解。 - Donbeo
尝试让这个问题更具可重现性,毕竟这不是你第一次发布问题了... - David Arenburg
我已经添加了一个例子 - Donbeo
6个回答

3
x.or.na <- function(x, df) if (x %in% names(df)) df[[x]] else NA
as.data.frame(Map(x.or.na, columns, list(d)))

2
set.seed(42)
 DF <- setNames(as.data.frame(matrix(sample(1:15, 15, replace=TRUE), ncol=3)), c('f', 'u', 'z') )

  DF
  #  f  u  z
  #1 14  8  7
  #2 15 12 11
  #3  5  3 15
  #4 13 10  4
  #5 10 11  7

 res <- do.call(`data.frame`,lapply(split(letters[4:26], letters[4:26]), 
       function(x){x1 <- match(x, colnames(DF)); if(!is.na(x1)) DF[,x1] else NA}))

 res    
 #  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z
 #1 NA NA 14 NA NA NA NA NA NA NA NA NA NA NA NA NA NA  8 NA NA NA NA  7
 #2 NA NA 15 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 12 NA NA NA NA 11
 #3 NA NA  5 NA NA NA NA NA NA NA NA NA NA NA NA NA NA  3 NA NA NA NA 15
 #4 NA NA 13 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 10 NA NA NA NA  4
 #5 NA NA 10 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 11 NA NA NA NA  7

使用dplyr
 library(dplyr)
   DF %>% 
   do({x1 <-data.frame(., setNames(as.list(rep(NA, sum(!letters[4:26] %in% names(DF)))), 
  setdiff(letters[4:26], names(DF))))
    x1[,order(colnames(x1))] })    
  #  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z
 #1 NA NA 14 NA NA NA NA NA NA NA NA NA NA NA NA NA NA  8 NA NA NA NA  7
 #2 NA NA 15 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 12 NA NA NA NA 11
 #3 NA NA  5 NA NA NA NA NA NA NA NA NA NA NA NA NA NA  3 NA NA NA NA 15
 #4 NA NA 13 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 10 NA NA NA NA  4
 #5 NA NA 10 NA NA NA NA NA NA NA NA NA NA NA NA NA NA 11 NA NA NA NA  7

在你的 ifelse 中,length(letters) %in% colnames(DF) 不等于 length(DF),所以如果你进行循环使用,得到期望的结果只是运气问题。建议尝试将 letters 替换为 letters[4:26] 等其他选项。此外,cbind 会强制所有数据类型相同,这并不是一个好的选择。 - flodel
@flodel,抱歉,没有注意到。非常感谢您指出这一点。 - akrun
仍然有些问题(抱歉...),尝试添加 DF$k <- TRUE - flodel
你的解决方案假设了DF中列的顺序;我建议在创建后添加DF$k,使k成为最后一列,但你可以尝试DF <- rev(DF)进行测试。此外,TRUE列被转换为1,因此远离cbind并没有帮助。 - flodel

2
这里有几种方法和它们的时间。
createDF1 <- function(colVec, data)
{
    m <- matrix(, nrow = nrow(data), ncol = length(colVec), 
                dimnames = list(NULL, colVec))
    m[, names(data)] <- as.matrix(data)
    data.frame(apply(m, 2, as.numeric))
} 

createDF2 <- function(colVec, data)
{
    rr <- setNames(rep(list(rep(NA_integer_, nrow(data))), length(colVec)),  .
                   nm = colVec)
    rr[match(names(data), colVec)] <- data
    as.data.frame(rr)
}

createDF3 <- function(colVec, data)
{
    rr <- setNames(replicate(length(colVec),  
                             list(rep(NA_integer_, nrow(data)))),  
                   nm = colVec)
    rr[match(names(d), colVec)] <- data
    as.data.frame(rr)
}

创建一个 3,000,000 x 3 的数据框以进行测试:
columns <- letters[1:21]
d <- data.frame(g = 1:3e6L, s = 1:3e6L, j = 1:3e6L)

运行一些测试:

system.time({ createDF1(columns, d) })
#  user  system elapsed 
# 5.022   1.023   6.054  
system.time({ createDF2(columns, d) })
#  user  system elapsed 
# 0.007   0.004   0.011 
system.time({ createDF3(columns, d) })
#  user  system elapsed 
# 0.105   0.077   0.183

在这三个选项中,看起来 rep(list(rep(NA_integer_, nrow(data))), length(columns)) 是最好的选择,并从中替换值。

createDF2之所以快速,是因为列表的每个元素都指向相同的值。分配只在第一次进行。其余部分是浅复制。执行:.Internal(inspect(rep(list(rep(NA, 10)), 5)))并检查地址。它们中的每一个都是相同的。这不是真正的快速,只是将耗时的操作(内存分配)推迟到下一步。如果您没有对其进行任何操作(直接写入文件),则具有优势。但我怀疑这种情况经常发生。 - Arun
我对它进行了 Rprof,除了 print 和它的方法之外,几乎没有什么东西。它之所以快,至少部分原因是因为我使用了 NA_integer_ 而不是 NA(逻辑值)创建了新结构。 - Rich Scriven
嗯,也许吧...也许不是。 :-) - Rich Scriven
我不确定你在使用Rprof时想表达什么。NA的分配只发生一次(即,分配一个大小为3百万整数的向量,一次)。不是21次。所以我对Rprof显示的很少并不感到惊讶。而且,你认为将NA更改为NA_integer_会使代码更快,这到底是什么原因呢?它们都需要4个字节的存储空间。 - Arun
检查时间:system.time(rep(NA_integer_,3e6))system.time(rep(list(rep(NA_integer_,3e6)),300L))。 在我的系统上,需要0.007与0.007。除非它们只是浅层复制,否则怎么可能会发生这种情况... :)(就像R v3.1+一样)。 - Arun
@Arun - 我只是按照 Duncan TL 在大学教给我的做法,哈哈。不过我们从来没有真正涉及到内存和存储,这很遗憾,因为 microbenchmark 似乎是 SO R 标签中最常被调用的函数。 :) 这都是一个学习的过程。 - Rich Scriven

2

使用data.table包可以轻松(语法方面)且高效(速度方面)地完成此操作:

require(data.table) ## 1.9.2+
setDT(d)[, setdiff(columns, names(d)) := NA] ## (1)
setcolorder(d, columns) ## (2)
setDF(d) ## (3)
  1. setDTd转换为data.table,然后我们使用:=运算符通过引用创建新列。有许多使用:=的方法,但这里突出显示了使用LHS := RHS的用例。这里LHS是一组列名向量,RHS是值。只在RHS上提供了一个NA,它会自动循环所有其他列。请注意,默认情况下,在R中,NA是逻辑类型。
  2. 如果需要,您可以使用setcolorderd的列重新按照与列相同的顺序排序。
  3. 同样,如果必要,您可以使用函数setDFdata.table转换回data.frame,该函数也通过引用修改对象。但是,目前仅在开发版本v1.9.3中可用。

0

[<- 可以用来将缺失的列填充为 NA

`[<-`(d,, setdiff(columns, names(d)), NA)[columns]
#`[<-`(d,, columns[!columns %in% names(d)], NA)[columns] #Alternative
#   a  b  c  d  e  f g  h  i  j  k  l  m  n  o  p  q  r s  t  u
#1 NA NA NA NA NA NA 1 NA NA NA NA NA NA NA NA NA NA NA 4 NA NA
#2 NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA NA NA 2 NA NA
#3 NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA NA NA 3 NA NA

或者直接将缺失的列添加到原始的data.frame

d[columns[!columns %in% names(d)]] <- NA
d[columns]
#   a  b  c  d  e  f g  h  i  j  k  l  m  n  o  p  q  r s  t  u
#1 NA NA NA NA NA NA 1 NA NA NA NA NA NA NA NA NA NA NA 4 NA NA
#2 NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA NA NA 2 NA NA
#3 NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA NA NA 3 NA NA

或者在一个函数中:

f <- function(DF, COL) {
  d[columns[!columns %in% names(d)]] <- NA
  d[columns]
}
f(d, columns)
#   a  b  c  d  e  f g  h  i  j  k  l  m  n  o  p  q  r s  t  u
#1 NA NA NA NA NA NA 1 NA NA NA NA NA NA NA NA NA NA NA 4 NA NA
#2 NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA NA NA 2 NA NA
#3 NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA NA NA 3 NA NA

数据

d <- data.frame('g'=c(1,2,3), 's' = c(4,2,3))
columns <- letters[1:21]

0

设置:

set.seed(1)
DF_all <- setNames(data.frame(matrix(rnorm(5*26), nrow=5, ncol=26)), letters)
DF <- DF_all[, c('f','u','z')]

创建一个新的空数据框并填充您的列:
DF2 <- setNames(data.frame(matrix(nrow=5, ncol=26)), letters)
DF2[, c('f','u','z')] <- DF[, c('f','u','z')]

结果:

> DF2
   a  b  c  d  e           f  g  h  i  j  k  l  m  n  o  p  q  r  s  t           u  v  w  x  y           z
1 NA NA NA NA NA -0.05612874 NA NA NA NA NA NA NA NA NA NA NA NA NA NA -0.62036668 NA NA NA NA  0.71266631
2 NA NA NA NA NA -0.15579551 NA NA NA NA NA NA NA NA NA NA NA NA NA NA  0.04211587 NA NA NA NA -0.07356440
3 NA NA NA NA NA -1.47075238 NA NA NA NA NA NA NA NA NA NA NA NA NA NA -0.91092165 NA NA NA NA -0.03763417
4 NA NA NA NA NA -0.47815006 NA NA NA NA NA NA NA NA NA NA NA NA NA NA  0.15802877 NA NA NA NA -0.68166048
5 NA NA NA NA NA  0.41794156 NA NA NA NA NA NA NA NA NA NA NA NA NA NA -0.65458464 NA NA NA NA -0.32427027

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接