清理 R 数据框,以便在列中没有行值大于下一行值的 2 倍

Posted

技术标签:

【中文标题】清理 R 数据框,以便在列中没有行值大于下一行值的 2 倍【英文标题】:Clean R data frame so that in a column no row value is bigger than 2 times next row value 【发布时间】:2015-03-29 00:52:45 【问题描述】:

我有一个如下示例的数据框

dist <- c(1.1,1.0,10.0,5.0,2.1,12.2,3.3,3.4)
id <- rep("A",length(dist))
df<-cbind.data.frame(id,dist)

df

  id dist
1  A  1.1
2  A  1.0
3  A 10.0
4  A  5.0
5  A  2.1
6  A 12.2
7  A  3.3
8  A  3.4

我需要清理它,所以 dist 列中的行值不会更大 任何时候都超过下一行值的 2 倍。清理后的数据框看起来 像这样:

  id dist
1  A  1.1
2  A  1.0
5  A  2.1
7  A  3.3
8  A  3.4

我尝试过使用 for 循环和 if 语句来清理它的函数

cleaner <-  function (df,dist,times_larger) 

              for (i in 1:(nrow(df)-1)) 

                  if (df$dist[i] > df$dist[i+1]*times_larger)
                    df<-df[-i,]
                    break       
                  
              
              df
            

显然,如果我不打破循环,它会产生错误,因为 df 中的行数将在此过程中发生变化。如果我手动运行循环 在 df 上多次:

df<-cleaner(df,"dist",2)

它会按照我的意愿进行清理。

我也尝试了不同的函数构造,并通过 apply 将其应用于数据框,但没有任何运气。

对于如何在数据帧上重复该函数直到它不再改变、更好的函数结构或更好的清理方式,是否有任何好的建议?

任何建议都非常感谢

【问题讨论】:

你能试试 fix() 函数吗?如果数据框不是很大,您可以手动完成。 【参考方案1】:

您可以将dist 的第一个元素向左移动,将其乘以2,然后与原始dist 进行比较:

subset(df,dist < c(2*dist[-1],Inf))
#  id dist
#1  A  1.1
#2  A  1.0
#5  A  2.1
#7  A  3.3
#8  A  3.4

【讨论】:

【参考方案2】:

你可以试试 leaddplyr

library(dplyr) #dplyr_0.4.0
filter(df, dist < 2 * lead(dist, default = Inf)) 
#    id dist
#1  A  1.1
#2  A  1.0
#3  A  2.1
#4  A  3.3
#5  A  3.4

或者使用data.table中的类似方法。开发版data.table中引入了一个新函数shift。我们可以指定类型为lead。默认情况下,它是lagfill 是 NA。将fill 修改为“Inf”(灵感来自@Marat Talipov 的帖子)。

library(data.table) #data.table_1.9.5
setDT(df)[dist <2 *shift(dist,type='lead', fill=Inf)]
#   id dist
#1:  A  1.1
#2:  A  1.0
#3:  A  2.1
#4:  A  3.3
#5:  A  3.4

更新

如果“dist”的值等于“2”乘以下一个值,则上述解决方案会删除该行。在这种情况下,

setDT(df)[dist <2 *(shift(dist,type='lead',
             fill=Inf)+.Machine$double.eps)]
#    id dist
#1:  A  1.1
#2:  A  1.0
#3:  A  2.1
#4:  A  3.3
#5:  A  3.4

使用@Henrik 评论的不同示例。

df1 <- data.frame(dist= as.numeric(3:1))
setDT(df1)[dist <2 *(shift(dist,type='lead', 
            fill=Inf)+.Machine$double.eps)]
#    dist
#1:    3
#2:    2
#3:    1

基准测试

set.seed(49)
df <- data.frame(id='A', dist=rnorm(1e7,20))
df1 <- copy(df)
akrun1 <- function() filter(df, dist < 2 * lead(dist,
                                 default = Inf)) 
akrun2 <- function() setDT(df1)[dist <2 *shift(dist,type='lead',
                                     fill=Inf)]
marat <- function() subset(df,dist < c(2*dist[-1],Inf))
Colonel <- function() df[with(df, dist<2*c(dist[-1], tail(dist,1))),]

library(microbenchmark)
microbenchmark(akrun1(), akrun2(), marat(), Colonel(), 
                                unit='relative', times=20L)
#Unit: relative
#    expr      min       lq     mean   median       uq      max neval  cld
# akrun1() 2.029087 1.990739 1.864697 1.965247 1.773722 1.727474    20  b  
# akrun2() 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000    20  a   
# marat() 8.032147 8.137982 7.359821 7.937062 7.134686 5.837623     20  d
#Colonel() 7.094465 7.045000 6.473552 6.903460 6.197737 5.359575    20  c 

【讨论】:

@Henrik 你是对的。谢谢。所有当前的解决方案确实只返回 2 个值。 filter(df, dist &lt; 2 * lead(dist, default = Inf)) 为我工作 @hadley 非常感谢,我使用了 dplyr 版本,因为我的数据框有几个子组,这看起来很自然。唯一的问题是我必须过滤几次;当值 > 2*lead 被过滤掉时,生成的数据帧可能会引入小于 2* 滞后值的新出现的前导值。不过,在我的示例中并非如此,所以我很糟糕。再次感谢您快速而出色的回复。【参考方案3】:

基础 R 解决方案:

> df[with(df, dist<2*c(dist[-1], tail(dist,1))),]
  id dist
1  A  1.1
2  A  1.0
5  A  2.1
7  A  3.3
8  A  3.4

如果没有零元素:

df[with(df, dist/c(dist[-1], tail(dist,1)))<2,]

【讨论】:

但如果某些dist 元素为零怎么办? 没错,您的乘法解决方案更适合一般情况!

以上是关于清理 R 数据框,以便在列中没有行值大于下一行值的 2 倍的主要内容,如果未能解决你的问题,请参考以下文章

计算前几行中大于当前行值的值

如何计算与 R 中相同列值关联的两个行值的差异?

如何创建一个新的 pandas 列,该列是索引范围中每个值的列表,不包括行值

用熊猫在列中绘制带有年份的数据框

如何使用 Pandas 在列中添加值的超链接?

一列中相同值的R子集行取决于另一列中的多个值