【问题标题】:First and Last value before NANA 之前的第一个和最后一个值
【发布时间】:2018-02-18 21:47:30
【问题描述】:

我试图在向量中的 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.

【问题讨论】:

  • @Ronak Shah 如果您不打算提供解决方案,为什么还要编辑?
  • 没有必要为编辑问题提供解决方案。 :)

标签: r dataframe vector logic


【解决方案1】:

使用c++ 函数进行一些循环在大集合上会很快。

此函数返回一个 2 列矩阵,第一列给出数字序列的“开始”,第二列给出序列的“结束”。

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))
}

【讨论】:

  • 查看我对 www 的回答的评论 - 你似乎也有同样的问题 - 例如,第一组应该是 1 to 1 而不是 1 to 11。但这确实提醒我,我需要学习一些 C++ 来加速这类操作。
  • @thelatemail - 感谢您的指点。我将“结果我们将 1;11 和 13;15 和 44;61 和 119;122”读作“分号分隔”组,然后用“和”分隔组。我会更新...
  • @thelatemail - 真的很值得(学习 Rcpp) - 非常简单,只要你掌握了窍门,而且速度提升通常很好。
  • @www - 你的rle 方法的效率让我吃惊
  • @SymbolixAU 非常感谢您提供此解决方案。我在一个相当大的数据集上尝试了 1000 万多条记录,并且效果很好!
【解决方案2】:

为非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 可能比splitting 和lapplying 慢一点:

lapply(split(xx[!nas], cumsum(nas)[!nas]), function(x) x[unique(c(1,length(x)))] )

【讨论】:

  • 非常感谢您的帮助!这个解决方案效果最好!我喜欢stackoverflow!
  • 再问一个问题....而不是结果是因素,我怎么能把它们变成一个角色? 'as.character'?
【解决方案3】:

我能想到的最简单的解决方案是使用tidyverse。首先使用来自 OP 的向量创建一个data.frame。然后添加 (mutate) 具有所需值的列。

使用leadlag 将提供从previousnext 行获取非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

【讨论】:

    【解决方案4】:

    我们可以使用rlecumsum

    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 &lt;- 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
    

    【讨论】:

    • 但是分组不是1-11; 13-15; 44-61等,所以矩阵输出有点误导。
    • @thelatenmail 感谢您的评论。我认为这与 OP 想要的输出相同,所以我不认为这是误导。
    • @thelatemail 等等,我明白你在说什么。我将更新我的答案以反映分组情况。
    • @thelatemail 请查看我的更新。我决定在创建矩阵输出时将NA 添加到第一个和最后一个索引。再次感谢您的评论。
    • 我讨厌讨厌,但这仍然不太正确 - 尝试使用 xx[c(2:10, 14, 45:60, 120)] = NA,最后一组应该是 121 to 122,但最终是 121 to NA
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-30
    • 2020-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-08
    • 2022-01-03
    相关资源
    最近更新 更多