0

我一直在尝试学习 R 中“if”和“for”的用法。作为一个简单的例子,我设置了数据框

V1<-c(3,2,2,4,5)
V2<-c(3,7,3,5,2)
V3<-c(5,2,5,7,5)
V4<-c(1,1,2,3,4)
V5<-c(1,2,6,7,5)
DF2<-data.frame(V1=V1,V2=V2,V3=V3,V4=V4,V5=V5)
DF2
  V1 V2 V3 V4 V5
1  3  3  5  1  1
2  2  7  2  1  2
3  2  3  5  2  6 
4  4  5  7  3  7
5  5  2  5  4  5

我的目标是设置一个 if 语句,该语句将根据行值删除一行中的一列。举个例子:

If V1 = 5, drop column V5
If V1 = 4, drop column V4 & V5

所以根据这些规则,我的数据框最终看起来像这样

  V1 V2 V3 V4 V5
1  3  3  5  1  1
2  2  7  2  1  2
3  2  3  5  2  6 
4  4  5  7  
5  5  2  5  4  

我的第一个想法是我可以写一个简单的 if 语句来做到这一点:

if(DF2$V1==5){
    DF2[-5]
}else if(DF2$V1==4){
    DF2[-4:5]
}

但我得到一个错误,“if”条件语句不能>1。所以我想如果我写了一个“for”循环,它将允许if语句逐行执行,允许条件语句为==1。

for(i in 1:length(DF2)){
if(DF2$V1==5){
    DF2[-5]
}else if(DF2$V1==4){
    DF2[-4]
}
} 

但现在我得到了同样的错误,只是 x10。所以我显然是在同一棵树上吠叫。所以我的问题是 - 处理我的原始数据框问题的最佳方法是什么?如果没有某种 if 或 for 循环答案,为什么会出现此错误?

4

3 回答 3

14

这不会帮助您使用if,因为您不应该if在这里使用,而是将解决方案矢量化。此外,您真的不能只在数据框中放置漏洞,因为根据定义,数据框是等长向量的列表。我想你可以把它变成一个字符向量并用“”替换你想要的部分,但这可能没有用。另一种方法是使用print并将其打印为矩阵,并告诉它不要显示 NA 或缺失值。

长话短说:

  1. 矢量化
  2. 替换为 NA 不为空
  3. 将 NA 打印为空白将其转换为矩阵并使用打印函数的参数

这里是...

DF2[DF2$V1==5, 5] <- NA
DF2[DF2$V1==4, 4:5] <- NA
DF2

#If you want blanks printed.
M1 <- as.matrix(DF2)
rownames(M1) <- 1:nrow(M1)
print(M1, na.print="", quote=FALSE)
于 2012-09-16T18:23:25.043 回答
5

老实说,我认为@Tyler 的方法更有效——对于普通 R 用户来说,这肯定是一种更典型的方法——但如果你执着于使用if,只需考虑一下你在做什么:

  • 您正在data.frame逐行处理。
  • R 中的apply()函数允许您将您的指定MARGIN1(按行应用函数)或2(按列应用函数)。
  • 因此,您可以将条件设​​置为“函数”,apply()以便在每一行上使用,如下所示。

    t(apply(DF2, 1, function(x) { if(x[1] == 5) x[5] <- NA;
                                  if(x[1] == 4) x[4:5] <- NA;
                                  x} ))
    #      V1 V2 V3 V4 V5
    # [1,]  3  3  5  1  1
    # [2,]  2  7  2  1  2
    # [3,]  2  3  5  2  6
    # [4,]  4  5  7 NA NA
    # [5,]  5  2  5  4 NA
    

t只是在最后一步中转置输出。

基准

一些评论中提出了效率问题。在小数据集的情况下,我怀疑任何答案在效率上会有很大差异,所以我用更大(但仍然很小)的数据集做了一些基准测试。

这是数据集:

set.seed(1)
DF2 = data.frame(V1 = sample(5, 1000, replace = TRUE),
                 V2 = sample(5, 1000, replace = TRUE),
                 V3 = sample(5, 1000, replace = TRUE),
                 V4 = sample(5, 1000, replace = TRUE),
                 V5 = sample(5, 1000, replace = TRUE))

这是用于运行基准测试的代码和结果。在这里,我们可以很容易地看到 Tyler 的方法比使用if (...) else if (...).

library(rbenchmark)
benchmark(
  Barranka = {
    for(i in seq(1,nrow(DF2))) {
      if(DF2$V1[i] == 5) {
        DF2[i,5] <- NaN
      } else if(DF2$V1[i] == 4) {
        DF2[i,4] <- NaN
        DF2[i,5] <- NaN
      }
    }},
  Tyler = {
    DF2[DF2$V1==5, 5] <- NA
    DF2[DF2$V1==4, 4:5] <- NA },
  mrdwab = {
    t(apply(DF2, 1, function(x) { if(x[1] == 5) x[5] <- NA;
                                  if(x[1] == 4) x[4:5] <- NA;
                                   x })) },
columns = c("test", "replications", "elapsed", "relative"), 
order = "relative")
#       test replications elapsed relative
# 2    Tyler          100   0.378    1.000
# 3   mrdwab          100   2.072    5.481
# 1 Barranka          100  11.885   31.442

当我尝试将行数更改为 100000 时,使用system.time()Tyler 的方法和我的方法能够毫无问题地做必要的事情。Tyler 的经过时间是 0.315 秒,我的是 2.773 秒,Barranka 是 807.446 秒(13 多分钟!)。这是一个巨大的差异。

如果有人知道更好的基准测试方法,请随时编辑和更新这篇文章。

注意:这不是在这里批评任何人的特定方法,而是为了证明评论中的一些陈述是正确的。我喜欢(和讨厌)R 的一件事是几乎总是有不止一种方法可以做某事。

于 2012-09-16T19:00:23.857 回答
-4

好的,无需深入挖掘细节,我认为您应该使用 afor来遍历数据框,并使用 aif来执行删除。另一方面,您不能拥有具有不同行大小的数据框,因此将所需条目替换为NaN

V1<-c(3,2,2,4,5)
V2<-c(3,7,3,5,2)
V3<-c(5,2,5,7,5)
V4<-c(1,1,2,3,4)
V5<-c(1,2,6,7,5)
DF2<-data.frame(V1=V1,V2=V2,V3=V3,V4=V4,V5=V5)

'The data frame, before replacing values:'; DF2
for(i in seq(1,nrow(DF2))) {
  if(DF2$V1[i] == 5) {
    DF2[i,5] <- NaN
  } else if(DF2$V1[i] == 4) {
    DF2[i,4] <- NaN
    DF2[i,5] <- NaN
  }
}

'The data frame, after replacing values:'; DF2

运行此脚本时,您将获得以下输出:

> V1<-c(3,2,2,4,5)
> V2<-c(3,7,3,5,2)
> V3<-c(5,2,5,7,5)
> V4<-c(1,1,2,3,4)
> V5<-c(1,2,6,7,5)
> DF2<-data.frame(V1=V1,V2=V2,V3=V3,V4=V4,V5=V5)
> 
> 'The data frame, before replacing values:'; DF2
[1] "The data frame, before replacing values:"
  V1 V2 V3 V4 V5
1  3  3  5  1  1
2  2  7  2  1  2
3  2  3  5  2  6
4  4  5  7  3  7
5  5  2  5  4  5
> for(i in seq(1,nrow(DF2))) {
+   if(DF2$V1[i] == 5) {
+     DF2[i,5] <- NaN
+   } else if(DF2$V1[i] == 4) {
+     DF2[i,4] <- NaN
+     DF2[i,5] <- NaN
+   }
+ }
> 
> 'The data frame, after replacing values:'; DF2
[1] "The data frame, after replacing values:"
  V1 V2 V3  V4  V5
1  3  3  5   1   1
2  2  7  2   1   2
3  2  3  5   2   6
4  4  5  7 NaN NaN
5  5  2  5   4 NaN

以防万一,我使用了一个出色的 R 参考站点:statmethods.net

希望这可以帮助你

于 2012-09-16T18:25:54.623 回答