valores - ¿Cómo dividir un vector en grupos de secuencias regulares y consecutivas?
multiplicacion de vectores en r (5)
Podrías definir los puntos de corte fácilmente:
which(diff(v) != 1)
Basado en ese intento:
v <- c(1,3,4,5,9,10,17,29,30)
cutpoints <- c(0, which(diff(v) != 1), length(v))
ragged.vector <- vector("list", length(cutpoints)-1)
for (i in 2:length(cutpoints)) ragged.vector[[i-1]] <- v[(cutpoints[i-1]+1):cutpoints[i]]
Lo que resulta en:
> ragged.vector
[[1]]
[1] 1
[[2]]
[1] 3 4 5
[[3]]
[1] 9 10
[[4]]
[1] 17
[[5]]
[1] 29 30
Este algoritmo no es muy bueno, pero podrías escribir un código mucho más personalizado en función de la diff
:) ¡Buena suerte!
Tengo un vector, como c(1, 3, 4, 5, 9, 10, 17, 29, 30)
y me gustaría agrupar los elementos ''vecinos'' que forman una secuencia regular y consecutiva en un vector desigual Resultando en:
L1: 1
L2: 3,4,5
L3: 9,10
L4: 17
L5: 29,30
Código ingenuo (de un programador ex-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 eso
a) R no es C (a pesar de las llaves)
b) las variables globales son pura maldad
c) que es una manera horriblemente ineficiente de lograr el resultado
, por lo que cualquier solución mejor es bienvenida.
Haciendo uso intensivo de algunos modismos 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 y Aaron fueron muy acertados. Sin embargo, su código aún se puede hacer más de dos veces más rápido mediante el uso cuidadoso de los tipos correctos, enteros y lógicos:
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
Puede crear un data.frame
y asignar los elementos a los grupos usando diff
, cumsum
y cumsum
, luego agregue usando tapply
:
v.df <- data.frame(v = v)
v.df$group <- cumsum(ifelse(c(1, diff(v) - 1), 1, 0))
tapply(v.df$v, v.df$group, function(x) x)
$`1`
[1] 1
$`2`
[1] 3 4 5
$`3`
[1] 9 10
$`4`
[1] 17
$`5`
[1] 29 30
daroczig escribe "podrías escribir un código mucho más personalizado basado en diff
" ...
Aquí hay una forma:
split(v, cumsum(diff(c(-Inf, v)) != 1))
EDITAR (tiempos agregados):
Tommy descubrió que esto podría ser más rápido teniendo cuidado con los tipos; la razón por la que se hizo más rápido es que la split
es más rápida en los enteros, y en realidad es aún más rápida en los factores.
Aquí está la solución de Joshua; el resultado del cumsum
es numérico porque está siendo c
''d con 1
, entonces 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
Solo con 1L
el resultado es un número entero que 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, para referencia; también se divide 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á Joshua, con el resultado convertido a un número entero antes de la 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 que se split
en un vector entero son más o menos iguales; podría ser aún 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 transformo en un factor directamente; esto no se recomienda en general porque depende de la estructura de la clase de factor. Esto se hace aquí solo con fines de comparación.
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