Crear variable de agrupación para secuencias consecutivas y dividir vector

Resuelto letsrock asked hace 54 años • 0 respuestas

Tengo un vector, comoc(1, 3, 4, 5, 9, 10, 17, 29, 30) y me gustaría agrupar los elementos 'vecinos' que forman una secuencia consecutiva regular, es decir, un aumento en 1, en un vector irregular que da como resultado:

L1: 1
L2: 3,4,5
L3: 9,10
L4: 17
L5: 29,30

Código ingenuo (de un ex programador de C):

partition.neighbors <- function(v)
{
    result <<- list() #jagged array
    currentList <<- v[1] #current series

    for(i in 2:length(v))
    {
        if(v[i] - v [i-1] == 1)
        {
            currentList <<- c(currentList, v[i])
        }
        else
        {
            result <<- c(result, list(currentList))
            currentList <<- v[i] #next series
        }       
    }

    return(result)  
}

Ahora entiendo que

a) R no es C (a pesar de las llaves)
b) las variables globales son pura maldad
c) esa es una forma terriblemente ineficiente de lograr el resultado

, por lo que cualquier solución mejor es bienvenida.

letsrock avatar Jan 01 '70 08:01 letsrock
Aceptado

Haciendo un uso intensivo de algunos modismos de R:

> split(v, cumsum(c(1, diff(v) != 1)))
$`1`
[1] 1

$`2`
[1] 3 4 5

$`3`
[1]  9 10

$`4`
[1] 17

$`5`
[1] 29 30
Joshua Ulrich avatar Mar 07 '2011 16:03 Joshua Ulrich

daroczig escribe "se podría escribir un código mucho más claro basado endiff "...

Aquí hay una forma:

split(v, cumsum(diff(c(-Inf, v)) != 1))

EDITAR (horarios agregados):

Tommy descubrió que esto podía ser más rápido si tenía cuidado con los tipos; La razón por la que se hizo más rápido es quesplit es más rápido con números enteros y, de hecho, es aún más rápido con factores.

Aquí está la solución de Joshua; el resultado de cumsumes numérico porque se compara ccon 1, por lo que es el más lento.

system.time({
a <- cumsum(c(1, diff(v) != 1))
split(v, a)
})
#   user  system elapsed 
#  1.839   0.004   1.848 

Simplemente chacer 1Lque el resultado sea un número entero lo acelera considerablemente.

system.time({
a <- cumsum(c(1L, diff(v) != 1))
split(v, a)
})
#   user  system elapsed 
#  0.744   0.000   0.746 

Esta es la solución de Tommy, como referencia; también se está dividiendo en un número entero.

> system.time({
a <- cumsum(c(TRUE, diff(v) != 1L))
split(v, a)
})
#   user  system elapsed 
#  0.742   0.000   0.746 

Aquí está mi solución original; también se está dividiendo en un número entero.

system.time({
a <- cumsum(diff(c(-Inf, v)) != 1)
split(v, a)
})
#   user  system elapsed 
#  0.750   0.000   0.754 

Aquí está el de Joshua, con el resultado convertido a un número entero antes del split.

system.time({
a <- cumsum(c(1, diff(v) != 1))
a <- as.integer(a)
split(v, a)
})
#   user  system elapsed 
#  0.736   0.002   0.740 

Todas las versiones splitde un vector entero son aproximadamente iguales; podría ser incluso más rápido si ese vector entero ya fuera un factor, ya que la conversión de un número entero a un factor en realidad lleva aproximadamente la mitad del tiempo. Aquí lo convierto directamente en un factor; En general, esto no se recomienda porque depende de la estructura de la clase de factor. Se hace aquí sólo con fines comparativos.

system.time({
a <- cumsum(c(1L, diff(v) != 1))
a <- structure(a, class = "factor", levels = 1L:a[length(a)])
split(v,a)
})
#   user  system elapsed 
#  0.356   0.000   0.357 
Aaron left Stack Overflow avatar Mar 07 '2011 16:03 Aaron left Stack Overflow

Joshua y Aaron dieron en el clavo. Sin embargo, su código aún se puede hacer más del doble de rápido mediante el uso cuidadoso de los tipos, enteros y lógicas correctos:

split(v, cumsum(c(TRUE, diff(v) != 1L)))

v <- rep(c(1:5, 19), len = 1e6) # Huge vector...
system.time( split(v, cumsum(c(1, diff(v) != 1))) ) # Joshua's code
# user  system elapsed 
#   2.64    0.00    2.64 

system.time( split(v, cumsum(c(TRUE, diff(v) != 1L))) ) # Modified code
# user  system elapsed 
# 1.09    0.00    1.12 
Tommy avatar Apr 08 '2011 00:04 Tommy