tablas - ¿Podemos obtener matrices de factores en R?
tablas en r (2)
Desafortunadamente, el soporte de factores no es completamente universal en R, por lo que muchas funciones de R tienen como valor predeterminado que los factores se traten como su tipo de almacenamiento interno, que es integer
:
> typeof(factor(letters[1:3]))
[1] "integer
Esto es lo que pasa con la matrix
, cbind
. No saben cómo manejar los factores, pero sí saben qué hacer con los números enteros, por lo que tratan su factor como un número entero. head
es en realidad lo opuesto. Sabe cómo manejar un factor, pero nunca se molesta en comprobar que su factor es también una matriz, por lo que simplemente lo trata como un vector de factor adimensional y normal.
Tu mejor apuesta para operar como si tuvieras factores con tu matriz es coaccionarla para que actúe. Una vez que haya terminado con sus operaciones, puede restaurarla de nuevo a la forma de factor. También puede hacer esto con la forma de entero, pero luego se arriesga a cosas extrañas (por ejemplo, podría hacer la multiplicación de matrices en una matriz de enteros, pero eso no tiene sentido para los factores).
Tenga en cuenta que si agrega clase "matriz" a su factor, algunas cosas (pero no todas) comienzan a funcionar:
f <- factor(letters[1:9])
dim(f) <- c(3, 3)
class(f) <- c("factor", "matrix")
head(f, 2)
Produce:
[,1] [,2] [,3]
[1,] a d g
[2,] b e h
Levels: a b c d e f g h i
Esto no arregla rbind
, etc.
Parece que no es posible obtener matrices de factor en R. ¿Es cierto? ¿Si es así por qué? Si no, ¿cómo debo hacer?
f <- factor(sample(letters[1:5], 20, rep=TRUE), letters[1:5])
m <- matrix(f,4,5)
is.factor(m) # fail.
m <- factor(m,letters[1:5])
is.factor(m) # oh, yes?
is.matrix(m) # nope. fail.
dim(f) <- c(4,5) # aha?
is.factor(f) # yes..
is.matrix(f) # yes!
# but then I get a strange behavior
cbind(f,f) # is not a factor anymore
head(f,2) # doesn''t give the first 2 rows but the first 2 elements of f
# should I worry about it?
En este caso, puede caminar como un pato e incluso patear como un pato, pero a partir de:
f <- factor(sample(letters[1:5], 20, rep=TRUE), letters[1:5])
dim(f) <- c(4,5)
realmente no es una matriz, aunque is.matrix()
afirma que es estrictamente una. Para ser una matriz en lo que concierne a is.matrix()
, f
solo necesita ser un vector y tener un atributo dim
. Añadiendo el atributo a f
pasas la prueba. Sin embargo, como ha visto, una vez que comienza a usar f
como matriz, pierde rápidamente las características que lo convierten en un factor (termina trabajando con los niveles o las dimensiones se pierden).
En realidad, solo hay matrices y matrices para los tipos de vectores atómicos:
- lógico,
- entero,
- real,
- complejo,
- cadena (o carácter), y
- crudo
además, como @hadley me recuerda, también puede tener matrices y matrices de listas (al establecer el atributo dim
en un objeto de lista. Consulte, por ejemplo, la sección Matrices y matrices del libro de Hadley, Advanced R ).
Cualquier cosa fuera de esos tipos sería forzada a algún tipo inferior a través de as.vector()
. Esto sucede en la matrix(f, nrow = 3)
no porque f
sea atómica de acuerdo con is.atomic()
(que devuelve TRUE
para f
porque se almacena internamente como un entero y typeof(f)
devuelve "integer"
), sino porque tiene un atributo de class
. Esto establece el bit OBJECT
en la representación interna de f
y se supone que todo lo que tenga una clase se debe forzar a uno de los tipos atómicos a través de as.vector()
:
matrix <- function(data = NA, nrow = 1, ncol = 1, byrow = FALSE,
dimnames = NULL) {
if (is.object(data) || !is.atomic(data))
data <- as.vector(data)
....
Agregar dimensiones a través de dim<-()
es una forma rápida de crear una matriz sin duplicar el objeto, pero esto omite algunas de las comprobaciones y balances que haría R si coercería f
a una matriz a través de los otros métodos.
matrix(f, nrow = 3) # or
as.matrix(f)
Esto se descubre cuando intenta usar funciones básicas que funcionan en matrices o usar el método de envío. Tenga en cuenta que después de asignar dimensiones a f
, f
aún es de clase "factor"
:
> class(f)
[1] "factor"
lo que explica el comportamiento de la head()
; no está obteniendo el comportamiento head.matrix
porque f
no es una matriz, al menos en lo que respecta al mecanismo S3:
> debug(head.matrix)
> head(f) # we don''t enter the debugger
[1] d c a d b d
Levels: a b c d e
> undebug(head.matrix)
y las head.default
método head.default
[
para las cuales existe un método factor
y, por lo tanto, el comportamiento observado:
> debugonce(`[.factor`)
> head(f)
debugging in: `[.factor`(x, seq_len(n))
debug: {
y <- NextMethod("[")
attr(y, "contrasts") <- attr(x, "contrasts")
attr(y, "levels") <- attr(x, "levels")
class(y) <- oldClass(x)
lev <- levels(x)
if (drop)
factor(y, exclude = if (anyNA(levels(x)))
NULL
else NA)
else y
}
....
El comportamiento de cbind()
se puede explicar a partir del comportamiento documentado (de ?cbind
, énfasis mío):
Las funciones
cbind
yrbind
son S3 genéricas , .......
En el método predeterminado, todos los vectores / matrices deben ser atómicos (ver
vector
) o listas. No se permiten expresiones. Los objetos de lenguaje (como fórmulas y llamadas) y las pairlists se convertirán en listas: otros objetos (como nombres y punteros externos) se incluirán como elementos en un resultado de lista. Todas las clases que puedan tener las entradas se descartan (en particular, los factores se reemplazan por sus códigos internos).
Nuevamente, el hecho de que f
sea de clase "factor"
está derrotando porque se cbind
método de cbind
predeterminado y eliminará la información de los niveles y devolverá los códigos de enteros internos como observó.
En muchos aspectos, debe ignorar o al menos no confiar completamente en lo que le dicen las funciones de is.foo
, porque solo están usando pruebas simples para decir si algo es o no un objeto foo
. is.matrix()
y is.atomic()
están claramente equivocados cuando se trata de f
(con dimensiones) desde un punto de vista particular . También tienen razón en cuanto a su implementación o al menos su comportamiento puede entenderse a partir de la implementación; Creo que is.atomic(f)
no es correcto, pero si "si es de un tipo atómico" R Core significa "tipo" para ser lo que devuelve typeof(f)
entonces is.atomic()
es correcto. Una prueba más estricta es is.vector()
, que f
falla:
> is.vector(f)
[1] FALSE
porque tiene atributos más allá de un atributo de names
:
> attributes(f)
$levels
[1] "a" "b" "c" "d" "e"
$class
[1] "factor"
$dim
[1] 4 5
En cuanto a cómo debería obtener una matriz de factores, bueno, no puede, al menos si desea que retenga la información del factor (las etiquetas para los niveles). Una solución sería usar una matriz de caracteres, que retendría las etiquetas:
> fl <- levels(f)
> fm <- matrix(f, ncol = 5)
> fm
[,1] [,2] [,3] [,4] [,5]
[1,] "c" "a" "a" "c" "b"
[2,] "d" "b" "d" "b" "a"
[3,] "e" "e" "e" "c" "e"
[4,] "a" "b" "b" "a" "e"
y almacenamos los niveles de f
para uso futuro en caso de que perdamos algunos elementos de la matriz en el camino.
O trabajar con la representación entera interna:
> (fm2 <- matrix(unclass(f), ncol = 5))
[,1] [,2] [,3] [,4] [,5]
[1,] 3 1 1 3 2
[2,] 4 2 4 2 1
[3,] 5 5 5 3 5
[4,] 1 2 2 1 5
y siempre puedes volver a los niveles / etiquetas de nuevo a través de:
> fm2[] <- fl[fm2]
> fm2
[,1] [,2] [,3] [,4] [,5]
[1,] "c" "a" "a" "c" "b"
[2,] "d" "b" "d" "b" "a"
[3,] "e" "e" "e" "c" "e"
[4,] "a" "b" "b" "a" "e"
El uso de un marco de datos parece no ser ideal para esto, ya que cada componente del marco de datos se trataría como un factor separado, mientras que parece que se desea tratar la matriz como un factor único con un conjunto de niveles.
Si realmente desea hacer lo que quiere, que es tener una matriz de factores, lo más probable es que necesite crear su propia clase de S3 para hacer esto, además de todos los métodos para hacerlo. Por ejemplo, puede almacenar la matriz de factores como una matriz de caracteres, pero con la clase "factorMatrix"
, donde almacenó los niveles junto con la matriz de factores como un atributo adicional, por ejemplo. Luego necesitaría escribir [.factorMatrix
, que tomaría los niveles, luego usar el método [
predeterminado en la matriz y luego volver a agregar el atributo de los niveles]. Podrías escribir cbind
métodos cbind
y head
. Sin embargo, la lista de métodos requeridos crecerá rápidamente, pero una implementación simple puede ser adecuada y si hace que sus objetos tengan la clase c("factorMatrix", "matrix")
(es decir, heredar de la clase "matrix"
), c("factorMatrix", "matrix")
todas las propiedades / métodos de la clase "matrix"
(que eliminarán los niveles y otros atributos) para que al menos pueda trabajar con los objetos y ver dónde necesita agregar nuevos métodos para completar el comportamiento de la clase.