我试图在向量中的NA值之前获取不同段的第一个和最后一个值。这是一个例子:
xx = seq(1, 122, by = 1)
xx[c(2:10, 14, 45:60, 120:121)] = NA
反过来,我的结果我们会1; 11和13; 15和44; 61和119; 122。
答案 0 :(得分:4)
为非NA
值组cumsum(nas)[!nas]
创建一个常量的计数器,然后取每组非NA
值中的第一个和最后一个值:
nas <- is.na(xx)
by(xx[!nas], cumsum(nas)[!nas], function(x) x[unique(c(1,length(x)))] )
#cumsum(nas)[!nas]: 0
#[1] 1
#--------------
#cumsum(nas)[!nas]: 9
#[1] 11 13
#--------------
#cumsum(nas)[!nas]: 10
#[1] 15 44
#--------------
#cumsum(nas)[!nas]: 26
#[1] 61 119
#--------------
#cumsum(nas)[!nas]: 28
#[1] 122
如果速度是一个问题,by
可能比split
和lapply
慢一点:
lapply(split(xx[!nas], cumsum(nas)[!nas]), function(x) x[unique(c(1,length(x)))] )
答案 1 :(得分:4)
使用c++
函数进行一些循环将在一大组上快速进行。
此函数返回一个2列矩阵,第一列给出了&#39; start&#39;在数字序列中,第二列给出了&#39; end&#39;的序列。
library(Rcpp)
cppFunction('NumericMatrix naSeq(NumericVector myVec) {
int n = myVec.size();
NumericVector starts(n); // pre-allocate
NumericVector ends(n); // pre-allocate
starts.fill(NumericVector::get_na());
ends.fill(NumericVector::get_na());
int startCounter = 0;
int endCounter = 0;
bool firstNumber = !NumericVector::is_na(myVec[0]); // initialise based on first value
// groups are considered sequential numbers without an NA between them
for (int i = 0; i < (n-1); i++) {
if ( !NumericVector::is_na(myVec[i]) && NumericVector::is_na(myVec[i+1]) ) {
if (i == 0 && firstNumber) {
startCounter++;
}
ends[endCounter] = i + 1;
endCounter++;
}
if (NumericVector::is_na(myVec[i]) && !NumericVector::is_na(myVec[i+1]) ) {
if ( i == 0 && !firstNumber){
endCounter++;
}
starts[startCounter] = i + 2;
startCounter++;
}
}
int matSize = startCounter > endCounter ? startCounter : endCounter;
IntegerVector idx = seq(0, matSize);
NumericMatrix m(matSize, 2);
starts = starts[idx];
ends = ends[idx];
m(_, 0) = starts;
m(_, 1) = ends;
return m;
}')
naSeq(xx)
给出了
# [,1] [,2]
# [1,] NA 1
# [2,] 11 13
# [3,] 15 44
# [4,] 61 119
# [5,] 122 NA
如果你关心速度,这里是解决方案的快速基准。请注意,无论每个函数的结果的格式(甚至内容)如何,我都会从每个答案中按原样执行这些函数。
library(microbenchmark)
set.seed(123)
xx <- seq(1:1e6)
naXX <- sample(xx, size = 1e5)
xx[naXX] <- NA
mb <- microbenchmark(
late = { latemail(xx) },
sym = { naSeq(xx) },
www = { www(xx) },
mkr = { mkr(xx) },
times = 5
)
print(mb, order = "median")
# Unit: milliseconds
# expr min lq mean median uq max neval
# sym 22.66139 23.26898 27.18414 23.48402 27.85917 38.64716 5
# www 45.11008 46.69587 55.73575 56.97421 61.63140 68.26719 5
# mkr 369.69303 384.15262 427.35080 392.26770 469.59242 521.04821 5
# late 2417.21556 2420.25472 2560.41563 2627.19973 2665.19272 2672.21543 5
使用
latemail <- function(xx) {
nas <- is.na(xx)
by(xx[!nas], cumsum(nas)[!nas], function(x) x[unique(c(1,length(x)))] )
}
www <- function(xx) {
RLE <- rle(is.na(xx))
L <- RLE$lengths
Index <- cumsum(L[-length(L)]) + (1:(length(L) - 1) + 1) %% 2
matrix(c(Index[1], NA, Index[2:length(Index)], NA), ncol = 2, byrow = TRUE)
}
library(dplyr)
mkr <- function(xx) {
df <- data.frame(xx = xx)
df %>% mutate(value = ifelse(is.na(xx), ifelse(!is.na(lag(xx)), lag(xx),
ifelse(!is.na(lead(xx)),lead(xx), NA)), NA)) %>%
select(value) %>%
filter(!is.na(value))
}
答案 2 :(得分:2)
我能想到的最简单的解决方案是使用tidyverse
。首先使用OP中的向量创建data.frame
。然后添加(mutate
)具有所需值的列。
lead
和lag
的使用将提供从previous
或next
行获取非NA值的选项。与NA
对应的行将具有NA
值,以后可以过滤掉。
library(tidyverse)
xx = seq(1, 122, by = 1)
xx[c(2:10, 14, 45:60, 120:121)] = NA
df <- data.frame(xx = xx)
df %>% mutate(value = ifelse(is.na(xx), ifelse(!is.na(lag(xx)), lag(xx),
ifelse(!is.na(lead(xx)),lead(xx), NA)), NA)) %>%
select(value) %>%
filter(!is.na(value))
#Result
# value
#1 1
#2 11
#3 13
#4 44
#5 61
#6 119
#7 122
答案 3 :(得分:2)
我们可以使用rle
和cumsum
。
RLE <- rle(is.na(xx))
L <- RLE$lengths
Index <- c(1, cumsum(L) + (1:length(L) + 1) %% 2)
matrix(Index, ncol = 2, byrow = TRUE)
# [,1] [,2]
# [1,] 1 1
# [2,] 11 13
# [3,] 15 44
# [4,] 61 119
# [5,] 122 122
<强>解释强>
rle(is.na(xx))
创建is.na(xx)
的游程编码,其中包含每个NA和非NA组的长度。
RLE <- rle(is.na(xx))
RLE
# Run Length Encoding
# lengths: int [1:9] 1 9 3 1 30 16 59 2 1
# values : logi [1:9] FALSE TRUE FALSE TRUE FALSE TRUE ...
L <- RLE$lengths
提取每组的长度。
L <- RLE$lengths
L
# [1] 1 9 3 1 30 16 59 2 1
cumsum(L)
计算获得索引的所有长度的累积和。
cumsum(L)
# [1] 1 10 13 14 44 60 119 121 122
然后我们需要为那些偶数索引号添加一个。所以我们使用(1:length(L) + 1) %% 2
来指定。
(1:(length(L) - 1) + 1) %% 2
# [1] 0 1 0 1 0 1 0 1 0
通过梳理上述两个向量,我们可以得到最终结果。
Index <- c(1, cumsum(L) + (1:length(L) + 1) %% 2)
Index
# [1] 1 1 11 13 15 44 61 119 122 122
最后,我使用matrix(Index, ncol = 2, byrow = TRUE)
只是为了更清楚地查看结果。每行代表一个组。第一列表示每个组的起始索引,而第二列表示每个组的结束。
matrix(Index, ncol = 2, byrow = TRUE)
# [,1] [,2]
# [1,] 1 1
# [2,] 11 13
# [3,] 15 44
# [4,] 61 119
# [5,] 122 122