12

我有一个数据框,其中包含类似于下面的数字序列:

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

我需要的是找到 0 的 1、2 或 3 次重复的所有实例,其中前面的数字和后面的数字相同 - 即都是 1 或都 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

4 回答 4

14

这是使用rle()and的无环解决方案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现在经常使用这种类型的任务。


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

于 2013-02-25T13:29:58.400 回答
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。我们再次使用“折叠”选项连接(“粘贴”)这些序列,以防有多个匹配项。然后,我们使用第二个连接将序列放入 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

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

于 2013-02-25T17:22:20.920 回答
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
于 2013-02-25T12:44:39.857 回答
0

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

str.values <- paste(YOUR$COLUMN, collapse="") 
str.values2 <- gsub("ORIGINAL PATTERN","PATTERN TO REPLACE", str.values)
于 2020-05-25T14:11:25.277 回答