清理 R 数据框,使列中没有行值大于下一行值的 2 倍
Clean R data frame so that in a column no row value is bigger than 2 times next row value
我有一个数据框,示例如下
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 将其应用于数据框,但没有任何运气。
有没有关于如何在数据框上重复函数直到它不再改变、更好的函数结构或者更好的清理方法的好建议?
非常感谢任何建议
您可以尝试 lead
来自 dplyr
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
。默认情况下,它是 lag
并且 fill
是 NA。将 fill
修改为 'Inf'(灵感来自@Marat Talipov 的 post)。
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
您可以将 dist
列左移一个元素,乘以二,然后与原始 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
基础 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 <- 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 将其应用于数据框,但没有任何运气。
有没有关于如何在数据框上重复函数直到它不再改变、更好的函数结构或者更好的清理方法的好建议?
非常感谢任何建议
您可以尝试 lead
来自 dplyr
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
。默认情况下,它是 lag
并且 fill
是 NA。将 fill
修改为 'Inf'(灵感来自@Marat Talipov 的 post)。
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
您可以将 dist
列左移一个元素,乘以二,然后与原始 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
基础 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,]