8
votes

Vecteur segment selon que les valeurs soient ou non supérieures à un seuil dans R

J'ai un long vecteur et j'ai besoin de la diviser en segments selon un seuil. Un segment est des valeurs consécutives sur le seuil. Lorsque les valeurs tombent en dessous du seuil, le segment se termine et le segment suivant commence où les valeurs se croisent à nouveau au-dessus du seuil. Je dois enregistrer les indices de début et de fin de chaque segment.

ci-dessous est une implémentation inefficace. Quel est le moyen le plus rapide et le plus approprié d'écrire cela? C'est assez moche, je dois supposer qu'il y a une implémentation plus propre. xxx

edit: exécution de toutes les solutions

Merci pour toutes les réponses, cela a été utile et très instructif. Un petit test de toutes les cinq solutions est inférieur (les quatre fournis plus l'exemple original). Comme vous pouvez le constater, les quatre sont une amélioration énorme sur la solution d'origine, mais la solution de Khashaa est de loin le plus rapide. xxx


0 commentaires

4 Réponses :


4
votes
bgoldst <- function() with(rle(test.vec>threshold),t(matrix(c(0L,rep(cumsum(lengths),2L)[-length(lengths)]),2L,byrow=T)+1:0)[values,]);
simong <- function() findSegments(test.vec,threshold);
set.seed(1); test.vec <- rnorm(1e7,8,10); threshold <- 0;
identical(bgoldst(),unname(simong()));
## [1] TRUE
system.time({ bgoldst(); })
##    user  system elapsed
##   1.344   0.204   1.551
system.time({ simong(); })
##    user  system elapsed
##   0.656   0.109   0.762

0 commentaires

6
votes

Voici une autre option, principalement en utilisant quel code>. Les points de début et de fin sont déterminés en trouvant les éléments non consécutifs de la séquence code> clip code>.

> round(test.vec,1)
  [1]  20.7  15.7   4.3 -15.1  24.6   9.4  23.2  -4.5  16.9  20.9  13.2  -1.2
 [13]  22.6   7.7   6.0   6.6   4.1  21.3   5.3  16.7  11.4  16.7  19.6  16.7
 [25]  11.6   7.3   3.7   8.4  -4.5  11.7  -7.1   8.4 -18.5  12.8  22.5  11.0
 [37]  -3.3  11.1   6.9  -7.9  22.9  -3.7   3.5  -7.1  -5.9   3.5  13.2  20.0
 [49]  13.2  23.4  15.9  -5.0  -6.3  10.0  -6.2   4.7   2.1  26.4   5.9  27.3
 [61]  14.3 -12.4  28.4  30.9  18.2  11.4   5.7  -4.5   6.2  12.0  10.9  11.1
 [73]  -2.0  -9.0  -1.4  15.4  19.1  -1.6  -5.4   5.4   7.8  -5.6  15.2  13.8
 [85] -18.8   7.1  17.1   9.3  -3.9  22.6   1.7  28.9 -21.3  21.2   8.2 -15.4
 [97]   3.2 -10.2  -6.2  14.1


0 commentaires

3
votes

Voici une autre solution que je pense est plus simple. Notez que vous devez utiliser set.sed.sed (10) code>, pas set.sed , pour définir la graine du générateur de nombres aléatoires.

require(dplyr) # for lead() and lag()

set.seed(10)
test.vec <- rnorm(100, 8, 10)
threshold <- 0

in.segment <- (test.vec > threshold)
start <- which(c(FALSE, in.segment) == TRUE & lag(c(FALSE, in.segment) == FALSE)) - 1
end <- which(c(in.segment, FALSE) == TRUE & lead(c(in.segment, FALSE) == FALSE))
segments <- data.frame(start, end)

head(segments)
##   start end
## 1     1   2
## 2     4   6
## 3     8   8
## 4    10  16
## 5    18  21
## 6    23  23


0 commentaires

5
votes

J'aime pour les boucles code> pour la traduction sur RCPP code> est simple.

Rcpp::cppFunction('DataFrame findSegment(NumericVector x, double threshold) {
  x.push_back(-1);
  int n = x.size(), startind, endind; 
  std::vector<int> startinds, endinds;
  bool insegment = false;
  for(int i=0; i<n; i++){
    if(!insegment){
      if(x[i] > threshold){        
        startind = i + 1;
        insegment = true;          }
    }else{
      if(x[i] < threshold){
        endind = i;
        insegment = false;
        startinds.push_back(startind); 
        endinds.push_back(endind);
      }
    }
  }
  return DataFrame::create(_["start"]= startinds, _["end"]= endinds);
}')
set.seed(1); test.vec <- rnorm(1e7,8,10); threshold <- 0;
system.time(findSegment(test.vec, threshold))

#   user  system elapsed 
#  0.045   0.000   0.045 

# @SimonG's solution
system.time(findSegments(test.vec, threshold))
#   user  system elapsed 
#  0.533   0.012   0.548 


0 commentaires