Crear variable de agrupación para secuencias consecutivas y dividir vector
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.
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
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 cumsum
es numérico porque se compara c
con 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 c
hacer 1L
que 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 split
de 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
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