¿Cuál es el algoritmo detrás de la función `split ''de R core?
(1)
split
es una función especialmente importante en el núcleo R
Muchas de las respuestas de Stack Overflow que ofrecen soluciones R-base en la manipulación de datos dependen de ello.
Es la rutina workhorse de cualquier operación grupal.
También hay muchas preguntas cuya solución es solo una línea con
split
.
Mucha gente no sabe eso.
-
split.data.frame
puede dividir una matriz por fila; -
split.default
puede dividir un cuadro de datos por columna.
Tal vez la documentación de R en
split
no lo está haciendo muy bien.
Menciona el primer uso, pero no menciona el segundo.
Hay cuatro métodos para
split
en el núcleo R:
methods(split)
#[1] split.data.frame split.Date split.default split.POSIXct
Proporcionaré una respuesta explicando en profundidad cómo funcionan
split.data.frame
,
split.default
y el nivel
.Internal(split(x, f))
.
Otras respuestas son bienvenidas en el objeto "Fecha" y "POSIXct".
¿Cómo funciona
split.data.frame
?
function (x, f, drop = FALSE, ...)
lapply(split(x = seq_len(nrow(x)), f = f, drop = drop, ...),
function(ind) x[ind, , drop = FALSE])
Llama a
split.default
para dividir el vector de índice de la fila
seq_len(nrow(x))
, luego usa un bucle
lapply
para extraer las filas asociadas en una entrada de lista.
Esto no es estrictamente un método "data.frame". Divide cualquier objeto bidimensional por la primera dimensión, incluida la división de una matriz por filas .
¿Cómo funciona
split.default
?
function (x, f, drop = FALSE, sep = ".", lex.order = FALSE, ...)
{
if (!missing(...))
.NotYetUsed(deparse(...), error = FALSE)
if (is.list(f))
f <- interaction(f, drop = drop, sep = sep, lex.order = lex.order)
else if (!is.factor(f))
f <- as.factor(f)
else if (drop)
f <- factor(f)
storage.mode(f) <- "integer"
if (is.null(attr(x, "class")))
return(.Internal(split(x, f)))
lf <- levels(f)
y <- vector("list", length(lf))
names(y) <- lf
ind <- .Internal(split(seq_along(x), f))
for (k in lf) y[[k]] <- x[ind[[k]]]
y
}
-
si
x
no tiene clases (es decir, principalmente un vector atómico), se.Internal(split(x, f))
; -
de lo contrario, usa
.Internal(split())
para dividir el índice a lo largo dex
, luego usa un buclefor
para extraer elementos asociados en una entrada de lista.
Un vector atómico (ver
?vector
) es un vector con el siguiente modo:
- "lógico", "entero", "numérico", "complejo", "carácter" y "en bruto"
- "lista"
- "expresión"
Un objeto con clase ... Er ... hay tantos !! Permítanme darles tres ejemplos:
- "factor"
- "marco de datos"
- "matriz"
En mi opinión, el
split.default
no está bien escrito.
Hay tantos objetos con clases, pero
split.default
trataría de la misma manera a través de
"["
.
Esto funciona bien con "factor" y "data.frame" (¡así que dividiremos el marco de datos a lo largo de las columnas!), Pero definitivamente no funciona con una matriz de la forma que esperamos.
A <- matrix(1:9, 3)
# [,1] [,2] [,3]
#[1,] 1 4 7
#[2,] 2 5 8
#[3,] 3 6 9
split.default(A, c(1, 1, 2)) ## it does not split the matrix by columns!
#$`1`
#[1] 1 2 4 5 7 8
#
#$`2`
#[1] 3 6 9
En realidad, la regla de reciclaje se ha aplicado a
c(1, 1, 2)
y estamos haciendo lo mismo:
split(c(A), rep_len(c(1,1,2), length(A)))
¿Por qué el núcleo R no escribe otra línea para una "matriz", como
for (k in lf) y[[k]] <- x[, ind[[k]], drop = FALSE]
Hasta ahora, la única forma de dividir de forma segura una matriz por columnas es transponerla, luego
split.data.frame
y luego otra transposición.
lapply(split.data.frame(t(A), c(1, 1, 2)), t)
Otra solución a través de
lapply(split.default(data.frame(A), c(1, 1, 2)), as.matrix)
tiene errores si
A
es una matriz de caracteres.
¿Cómo funciona
.Internal(split(x, f))
?
¡Este es realmente el núcleo del núcleo! Tomaré un pequeño ejemplo a continuación para explicación:
set.seed(0)
f <- sample(factor(letters[1:3]), 10, TRUE)
# [1] c a b b c a c c b b
#Levels: a b c
x <- 0:9
Básicamente hay 3 pasos. Para mejorar la legibilidad, se proporciona un código R equivalente para cada paso.
paso 1: tabulación (conteo de ocurrencia de cada nivel de factor)
## a factor has integer mode so `tabulate` works
tab <- tabulate(f, nbins = nlevels(f))
[1] 2 4 4
paso 2: asignación de almacenamiento de la lista resultante
result <- vector("list", nlevels(f))
for (i in 1:length(tab)) result[[i]] <- vector(mode(x), tab[i])
names(result) <- levels(f)
Anotaría esta lista de la siguiente manera, donde cada línea es un elemento de lista que es un vector en este ejemplo, y cada
[ ]
es un marcador de posición para una entrada de ese vector.
$a: [ ] [ ]
$b: [ ] [ ] [ ] [ ]
$c: [ ] [ ] [ ] [ ]
paso 3: asignación de elementos
Ahora es útil para descubrir el modo de entero interno para un factor:
.f <- as.integer(f)
#[1] 3 1 2 2 3 1 3 3 2 2
Necesitamos escanear
x
y
.f
, rellenando
x[i]
en
la entrada correcta
del
result[[.f[i]]]
, Informado por un vector de búfer acumulador.
ab <- integer(nlevels(f)) ## accumulator buffer
for (i in 1:length(.f)) {
fi <- .f[i]
counter <- ab[fi] + 1L
result[[fi]][counter] <- x[i]
ab[fi] <- counter
}
En la siguiente ilustración,
^
es un puntero a los elementos a los que se accede o actualiza.
## i = 1
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [0] [0] [0] ## on entry
^
$a: [ ] [ ]
$b: [ ] [ ] [ ] [ ]
$c: [0] [ ] [ ] [ ]
^
ab: [0] [0] [1] ## on exit
^
## i = 2
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [0] [0] [1] ## on entry
^
$a: [1] [ ]
^
$b: [ ] [ ] [ ] [ ]
$c: [0] [ ] [ ] [ ]
ab: [1] [0] [1] ## on exit
^
## i = 3
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [1] [0] [1] ## on entry
^
$a: [1] [ ]
$b: [2] [ ] [ ] [ ]
^
$c: [0] [ ] [ ] [ ]
ab: [1] [1] [1] ## on exit
^
## i = 4
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [1] [1] [1] ## on entry
^
$a: [1] [ ]
$b: [2] [3] [ ] [ ]
^
$c: [0] [ ] [ ] [ ]
ab: [1] [2] [1] ## on exit
^
## i = 5
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [1] [2] [1] ## on entry
^
$a: [1] [ ]
$b: [2] [3] [ ] [ ]
$c: [0] [4] [ ] [ ]
^
ab: [1] [2] [2] ## on exit
^
## i = 6
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [1] [2] [2] ## on entry
^
$a: [1] [5]
^
$b: [2] [3] [ ] [ ]
$c: [0] [4] [ ] [ ]
ab: [2] [2] [2] ## on exit
^
## i = 7
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [2] [2] [2] ## on entry
^
$a: [1] [5]
$b: [2] [3] [ ] [ ]
$c: [0] [4] [6] [ ]
^
ab: [2] [2] [3] ## on exit
^
## i = 8
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [2] [2] [3] ## on entry
^
$a: [1] [5]
$b: [2] [3] [ ] [ ]
$c: [0] [4] [6] [7]
^
ab: [2] [2] [4] ## on exit
^
## i = 9
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [2] [2] [4] ## on entry
^
$a: [1] [5]
$b: [2] [3] [8] [ ]
^
$c: [0] [4] [6] [7]
ab: [2] [3] [4] ## on exit
^
## i = 10
x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
.f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2]
^
ab: [2] [3] [4] ## on entry
^
$a: [1] [5]
$b: [2] [3] [8] [9]
^
$c: [0] [4] [6] [7]
ab: [2] [4] [4] ## on exit
^