我有这个例子data.frame
:
df <- data.frame(id=c("a","a,b,c","d,e","d","h","e","i","b","c"), start=c(100,100,400,400,800,500,900,200,300), end=c(150,350,550,450,850,550,950,250,350), level = c(1,5,2,3,6,4,2,1,1))
> df
id start end level
1 a 100 150 1
2 a,b,c 100 350 5
3 d,e 400 550 2
4 d 400 450 3
5 h 800 850 6
6 e 500 550 4
7 i 900 950 2
8 b 200 250 1
9 c 300 350 1
其中每行是线性间隔。 如此示例所示,某些行是合并的间隔(第2行和第3行)。
我想要做的是,如果合并间隔的df
大于其所有部分的df$level
,则每个合并间隔要么从df$level
中删除所有单个部分,要么如果合并间隔的> res.df
id start end level
1 a,b,c 100 350 5
2 d 400 450 3
3 h 800 850 6
4 e 500 550 4
5 i 900 950 2
小于其中至少一个部分,则消除合并间隔。
因此,对于此示例,输出应为:
{{1}}
答案 0 :(得分:5)
因此,如果我们可以假设所有“合并”组的ID名称都是各个组的逗号分隔列表,那么我们只需查看ID并忽略开始/结束信息即可解决此问题。这是一种这样的方法
首先,通过使用逗号
查找ID来查找所有“合并”组groups<-Filter(function(x) length(x)>1,
setNames(strsplit(as.character(df$id),","),df$id))
现在,对于每个组,确定谁具有更高级别,合并组或其中一个组。然后返回要删除的行的索引作为负数
drops<-unlist(lapply(names(groups), function(g) {
mi<-which(df$id==g)
ii<-which(df$id %in% groups[[g]])
if(df[mi, "level"] > max(df[ii, "level"])) {
return(-ii)
} else {
return(-mi)
}
}))
最后,从data.frame中删除那些
df[drops,]
# id start end level
# 2 a,b,c 100 350 5
# 4 d 400 450 3
# 5 h 800 850 6
# 6 e 500 550 4
# 7 i 900 950 2
我还想尝试一种忽略(非常有用)合并ID名称的方法,只看一下开始/结束位置。我可能走错了方向,但这导致我将其视为网络/图形类型问题,因此我使用了igraph
库。
我创建了一个图表,其中每个顶点代表一个开始/结束位置。因此每个边缘代表一个范围。我使用了样本数据集中的所有范围并填充了任何缺失的范围以使图形连接。我将这些数据合并在一起以创建边缘列表。对于每个边缘,我记得原始数据集中的“level”和“id”值。这是执行该操作的代码
library(igraph)
poslist<-sort(unique(c(df$start, df$end)))
seq.el<-embed(rev(poslist),2)
class(seq.el)<-"character"
colnames(seq.el)<-c("start","end")
el<-rbind(df[,c("start","end","level", "id")],data.frame(seq.el, level=0, id=""))
el<-el[!duplicated(el[,1:2]),]
gg<-graph.data.frame(el)
这会创建一个看起来像
的图表
所以基本上我们想通过采用具有最大“级别”值的边的路径来消除图中的循环。不幸的是,因为这不是一个正常的路径加权方案,我没有找到一个简单的方法来使用默认算法(也许我错过了它)。所以我不得不编写自己的图横向函数。它并不像我希望的那样漂亮,但它就是这样。
findPath <- function(gg, fromv, tov) {
if ((missing(tov) && length(incident(gg, fromv, "in"))>1) ||
(!missing(tov) && V(gg)[fromv]==V(gg)[tov])) {
return (list(level=0, path=numeric()))
}
es <- E(gg)[from(fromv)]
if (length(es)>1) {
pp <- lapply(get.edges(gg, es)[,2], function(v) {
edg <- E(gg)[fromv %--% v]
lvl <- edg$level
nxt <- findPaths(gg,v)
return (list(level=max(lvl, nxt$level), path=c(edg,nxt$path)))
})
lvl <- sapply(pp, `[[`, "level")
take <- pp[[which.max(lvl)]]
nxt <- findPaths(gg, get.edges(gg, tail(take$path,1))[,2], tov)
return (list(level=max(take$level, nxt$level), path=c(take$path, nxt$path)))
} else {
lvl <- E(gg)[es]$level
nv <- get.edges(gg,es)[,2]
nxt <- findPaths(gg, nv, tov)
return (list(level=max(lvl, nxt$level), path=c(es, nxt$path)))
}
}
这将在两个节点之间找到一条路径,该路径满足具有分支时具有最大级别的属性。我们用
这个数据集来调用它rr <- findPaths(gg, "100","950")$path
这将找到最终路径。由于原始df
data.frame中的每一行都由边表示,因此我们只需要从路径中提取与最终路径对应的边。这实际上给了我们一条看起来像
其中红色路径是所选择的路径。然后,我可以使用
对df
进行分组
df[df$id %in% na.omit(E(gg)[rr]$id), ]
# id start end level
# 2 a,b,c 100 350 5
# 4 d 400 450 3
# 5 h 800 850 6
# 6 e 500 550 4
# 7 i 900 950 2
他是另一种看待开始/停止位置的方法。我创建了一个matix,其中列对应于data.frame行中的范围,矩阵的行对应于位置。如果范围与位置重叠,则矩阵中的每个值都为真。在这里,我使用between.R辅助函数
#find unique positions and create overlap matrix
un<-sort(unique(unlist(df[,2:3])))
cc<-sapply(1:nrow(df), function(i) between(un, df$start[i], df$end[i]))
#partition into non-overlapping sections
groups<-cumsum(c(F,rowSums(cc[-1,]& cc[-nrow(cc),])==0))
#find the IDs to keep from each section
keeps<-lapply(split.data.frame(cc, groups), function(m) {
lengths <- colSums(m)
mx <- which.max(lengths)
gx <- setdiff(which(lengths>0), mx)
if(length(gx)>0) {
if(df$level[mx] > max(df$level[gx])) {
mx
} else {
gx
}
} else {
mx
}
})
这将给出每个组保留的ID列表,我们可以使用
获取最终的data.setdf[unlist(keeps),]
我有最后一种方法。这个可能是最具扩展性的。我们基本上融化了头寸并跟踪开盘和结束事件以识别这些团体。然后我们拆分并查看每组中最长的是否具有最高级别。最终我们会返回ID。此方法使用所有标准基本函数。
#create open/close listing
dd<-rbind(
cbind(df[,c(1,4)],pos=df[,2], evt=1),
cbind(df[,c(1,4)],pos=df[,3], evt=-1)
)
#annotate with useful info
dd<-dd[order(dd$pos, -dd$evt),]
dd$open <- cumsum(dd$evt)
dd$group <- cumsum(c(0,head(dd$open,-1)==0))
dd$width <- ave(dd$pos, dd$id, FUN=function(x) diff(range(x)))
#slim down
dd <- subset(dd, evt==1,select=c("id","level","width","group"))
#process each group
ids<-unlist(lapply(split(dd, dd$group), function(x) {
if(nrow(x)==1) return(x$id)
mw<-which.max(x$width)
ml<-which.max(x$level)
if(mw==ml) {
return(x$id[mw])
} else {
return(x$id[-mw])
}
}))
最后是子集
df[df$id %in% ids, ]
到现在为止我想你知道这会带来什么
因此,如果您的真实数据与样本数据具有相同类型的ID,那么显然方法1是更好,更直接的选择。我仍然希望有一种简化方法2的方法,我只是缺少它。我没有对这些方法的效率或性能进行任何测试。我猜测方法4可能是最有效的,因为它应该线性扩展。
答案 1 :(得分:1)
我会采用程序方法;基本上,按级别降序排序, 并为每条记录删除以后具有匹配ID的记录。
df <- data.frame(id=c("a","a,b,c","d,e","d","h","e","i","b","c"), start=c(100,100,400,400,800,500,900,200,300), end=c(150,350,550,450,850,550,950,250,350),
level = c(1,5,2,3,6,4,2,1,1), stringsAsFactors=FALSE)
#sort
ids <- df[order(df$level, decreasing=TRUE), "id"]
#split
ids <- sapply(df$id, strsplit, ",")
i <- 1
while( i < length(ids)) {
current <- ids[[i]]
j <- i + 1
while(j <= length(ids)) {
if(any(ids[[j]] %in% current))
ids[[j]] <- NULL
else
j <- j + 1
}
i <- i + 1
}
最后,只保留剩下的ID:
R> ids <- data.frame(id=names(ids), stringsAsFactors=FALSE)
R> merge(ids, df, sort=FALSE)
id start end level
1 h 800 850 6
2 a,b,c 100 350 5
3 e 500 550 4
4 d 400 450 3
5 i 900 950 2
这有一些丑陋的while循环,因为R只有for-each循环,并且还注意stringsAsFactors=FALSE
是分割id所必需的。删除中间元素
可能对性能不利,但这取决于底层实现
R用于列表(链接与数组)。