在r中查找并替换数字序列

时间:2013-02-25 12:33:50

标签: r replace sequence

我有一个数据帧,其序列号如下:

data <- c(1,1,1,0,0,1,1,2,2,2,0,0,0,2,1,1,0,1,0,2)

我需要的是找到1,2或3次重复0的所有实例,其中前面和后面的数字是相同的 - 即1或2都是2(例如1,0,1或2,0,0) ,2但不是2,0,1)。

然后我只需要用周围的值填充零。

我设法找到并计算连续的零

consec <- (!data) * unlist(lapply(rle(data)$lengths, seq_len))

然后我找到了这些连续零开始的行:

consec <- as.matrix(consec)
first_na <- which(consec==1,arr.ind=TRUE)

但我对替换过程感到困惑

我真的很感谢你对此的帮助!

卡尔

4 个答案:

答案 0 :(得分:14)

以下是使用rle()inverse.rle()的无环路解决方案。

data <- c(1,1,1,0,0,1,1,2,2,2,0,0,0,2,1,1,0,1,0,2)

local({
  r <- rle(data)
  x <- r$values
  x0 <- which(x==0) # index positions of zeroes
  xt <- x[x0-1]==x[x0+1] # zeroes surrounded by same value
  r$values[x0[xt]] <- x[x0[xt]-1] # substitute with surrounding value
  inverse.rle(r)
})

[1] 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 2

PS。我使用local()作为一种简单的机制,不用大量新临时对象来破坏工作区。您可以创建function而不是使用local - 我现在发现我现在使用local进行此类任务。


PPS。您必须修改此代码以排除原始数据中的前导或尾随零。

答案 1 :(得分:2)

由于似乎对这个问题的答案很感兴趣,我想我会为后代写一个替代的正则表达式方法。

使用'gregexpr'功能,您可以搜索模式并使用结果位置匹配和匹配长度来调出要在原始矢量中更改的值。使用正则表达式的好处是我们可以明确地确定我们想要匹配的模式,因此,我们不会担心任何排除情况。

注意:以下示例按写入方式工作,因为我们假设为一位数值。我们可以轻松地将其用于其他模式,但我们可以使用单个字符的小捷径。如果我们想用可能的多位数值来做这件事,我们想要在第一个连接('粘贴')函数中添加一个分隔字符。


守则

str.values <- paste(data, collapse="") # String representation of vector
str.matches <- gregexpr("1[0]{1,3}1", str.values) # Pattern 101/1001/10001
data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 1 # Replace zeros with ones
str.matches <- gregexpr("2[0]{1,3}2", str.values) # Pattern 202/2002/20002
data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 2 # Replace zeros with twos

第1步:制作一个包含所有数据值的字符串。

str.values <- paste(data, collapse="")
# "11100112220002110102"

这会将数据折叠成一个长字符串,因此我们可以在其上使用正则表达式。

第2步:应用正则表达式查找字符串中任何匹配项的位置和长度。

str.matches <- gregexpr("1[0]{1,3}1", str.values)
# [[1]]
# [1]  3 16
# attr(,"match.length")
# [1] 4 3
# attr(,"useBytes")
# [1] TRUE

在这种情况下,我们使用正则表达式来查找第一个模式,一到三个零([0]{2,}),两边都有一个(1[0]{1,3}1)。我们必须匹配整个模式,以防止必须检查匹配的或两端的两个。我们将在下一步中减去这些目标。

第3步:将1写入原始向量中的所有匹配位置。

data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 1
# 1 1 1 1 1 1 1 2 2 2 0 0 0 2 1 1 1 1 0 2

我们在这里一步到位。首先,我们从正则表达式中匹配的数字创建一个数字序列列表。在这种情况下,有两个匹配,它们分别从索引3和16开始,分别是4和3项。这意味着我们的零位于索引(3 + 1):( 3-2 + 4),或4:5和(16 + 1):( 16-2 + 3)或17:17。我们再次使用'collapse'选项连接('粘贴')这些序列,以防有多个匹配。然后,我们使用第二个串联将序列放在combine(c())函数内。使用'eval'和'parse'函数,我们将此文本转换为代码并将其作为索引值传递给[data]数组。我们将所有这些写入这些位置。

步骤x :对每个模式重复一遍。在这种情况下,我们需要进行第二次搜索并找到一到三个零,两边都有两个,然后运行与步骤3相同的语句,但是分配两个而不是一个。

str.matches <- gregexpr("2[0]{1,3}2", str.values)
# [[1]]
# [1] 10
# attr(,"match.length")
# [1] 5
# attr(,"useBytes")
# [1] TRUE

data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 2
# 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 2

更新:我意识到原来的问题是连续匹配一到三个零,而不是我写入原始代码的“两个或更多”。我已经更新了正则表达式和解释,尽管代码保持不变。

答案 2 :(得分:1)

可能有一个没有for循环的解决方案,但你可以试试这个:

tmp <- rle(data)
val <- tmp$values
for (i in 2:(length(val)-1)) {
  if (val[i]==0 & val[i-1]==val[i+1]) val[i] <- val[i-1]
}
tmp$values <- val
inverse.rle(tmp)  

给出了:

[1] 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 2

答案 3 :(得分:0)

对于那些在2020年研究此问题的人,我仅使用gsub进行了序列替换。

<a>