Подтвердить что ты не робот

Можем ли мы получить фактор-матрицы в R?

Кажется невозможным получить матрицы фактора в R. Это правда? Если да, то почему? Если нет, как мне это сделать?

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?
4b9b3361

Ответ 1

В этом случае он может ходить как утка и даже шарлатан, как утка, но f от:

f <- factor(sample(letters[1:5], 20, rep=TRUE), letters[1:5])
dim(f) <- c(4,5)

действительно не является матрицей, хотя is.matrix() утверждает, что он строго один. Чтобы быть матрицей до is.matrix(), f должен быть только вектор и иметь атрибут dim. Добавив атрибут в f, вы пройдете тест. Однако, как вы видели, как только вы начнете использовать f в качестве матрицы, он быстро теряет функции, которые делают его фактором (вы в конечном итоге работаете с уровнями или размерами теряются).

Существуют действительно только матрицы и массивы для типов атомных векторов:

  • логична,
  • целое число,
  • реальный,
  • комплекс
  • строка (или символ) и
  • сырые

plus, как мне напоминает @hadley, вы также можете иметь матрицы и массивы списков (путем установки атрибута dim в объекте списка. См., например, Матрицы и массивы в разделе книги Хэдли, Advanced R.)

Все, что за пределами этих типов, будет привязано к некоторому более низкому типу через as.vector(). Это происходит в matrix(f, nrow = 3) не потому, что f является атомарным в соответствии с is.atomic() (который возвращает TRUE для f, потому что он внутренне хранится как целое число, а typeof(f) возвращает "integer"), но поскольку он имеет a class. Это устанавливает бит OBJECT во внутреннем представлении f, и все, что имеет класс, должно быть принуждено к одному из атомных типов через 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)
....

Добавление размеров через dim<-() - это быстрый способ создания массива без дублирования объекта, но это обходит некоторые из проверок и балансов, которые R будет делать, если вы принудили f к матрице с помощью других методов

matrix(f, nrow = 3) # or
as.matrix(f)

Это выясняется, когда вы пытаетесь использовать базовые функции, которые работают с матрицами или используют метод отправки. Обратите внимание, что после назначения размеров f, f все еще имеет класс "factor":

> class(f)
[1] "factor"

который объясняет поведение head(); вы не получаете поведение head.matrix, потому что f не является матрицей, по крайней мере, в отношении механизма 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)

и метод head.default вызывает [, для которого существует метод factor, и, следовательно, наблюдаемое поведение:

> 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
}
....

Поведение cbind() можно объяснить из документированного поведения (от ?cbind, выделенного мной):

Функции cbind и rbind являются S3 generic,...

....

В методе по умолчанию все векторы/матрицы должны быть атомарными      (см. vector) или списки. Выражения не допускаются. язык      объекты (такие как формулы и вызовы) и пары будут принудительно      к спискам: другие объекты (такие как имена и внешние указатели) будут      быть включенными в качестве элементов в результат списка. Любые классы входов      могут быть отброшены (в частности, факторы заменяются на      их внутренние коды).

Опять же, тот факт, что f имеет класс "factor", побеждает вас, потому что метод по умолчанию cbind будет вызван, и он будет лишать информацию о уровнях и возвращать внутренние целые коды, как вы заметили.

Во многом вы должны игнорировать или, по крайней мере, не полностью доверять тому, что говорят вам функции is.foo, потому что они просто используют простые тесты, чтобы сказать, является ли что-то или нет объектом foo. is.matrix() и is.atomic() явно ошибочны, когда дело доходит до f (с размерами) с определенной точки зрения. Они также правильны с точки зрения их реализации или, по крайней мере, их поведение можно понять из реализации; Я думаю, что is.atomic(f) неверен, но если "если имеет атомный тип" R Core означает "тип", то это вещь, возвращаемая typeof(f), тогда is.atomic() является правильной. Более строгий тест is.vector(), который f терпит неудачу:

> is.vector(f)
[1] FALSE

потому что он имеет атрибуты за пределами атрибута names:

> attributes(f)
$levels
[1] "a" "b" "c" "d" "e"

$class
[1] "factor"

$dim
[1] 4 5

Что касается того, как вы должны получить матрицу факторов, ну вы не можете, по крайней мере, если хотите, чтобы она сохранила информацию о факторах (метки для уровней). Одним из решений было бы использовать матрицу символов, которая сохранила метки:

> 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"

и мы сохраняем уровни f для будущего использования, если мы потеряем некоторые элементы матрицы по пути.

Или работайте с внутренним целочисленным представлением:

> (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

и вы всегда можете вернуться к уровням/методам снова через:

> 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"

Использование кадра данных представляется не идеальным для этого, так как каждый компонент кадра данных будет рассматриваться как отдельный фактор, тогда как вы, похоже, хотите рассматривать массив как один фактор с одним набором уровней.

Если вы действительно хотите делать то, что хотите, у которого есть фактор-матрица, вам, скорее всего, понадобится создать свой собственный класс S3 для этого, а также все методы, чтобы пойти с ним. Например, вы можете хранить фактор-матрицу как матрицу символов, но с классом "factorMatrix", где вы сохранили уровни вместе с матрицей факторов как дополнительный атрибут. Затем вам нужно написать [.factorMatrix, который будет захватывать уровни, а затем использовать по умолчанию метод [ в матрице, а затем снова добавить атрибут уровней. Вы могли бы написать методы cbind и head. Однако список требуемого метода будет быстро расти, но может потребоваться простая реализация, и если вы сделаете свои объекты классом c("factorMatrix", "matrix") (т.е. наследуем от класса "matrix"), вы получите все свойства/методы "matrix" class (который выведет уровни и другие атрибуты), чтобы вы могли хотя бы работать с объектами и посмотреть, где вам нужно добавить новые методы, чтобы заполнить поведение класса.

Ответ 2

К сожалению, поддержка коэффициента не является полностью универсальной в R, поэтому многие функции R по умолчанию обрабатывают факторы как свой внутренний тип хранения, который равен integer:

> typeof(factor(letters[1:3]))
[1] "integer  

Это то, что происходит с matrix, cbind. Они не знают, как обрабатывать факторы, но они знают, что делать с целыми числами, поэтому они обрабатывают ваш фактор как целое. head на самом деле противоположное. Он знает, как обрабатывать фактор, но он никогда не утруждает себя проверкой того, что ваш фактор также является матрицей, поэтому просто рассматривает его как нормальный безразмерный вектор факторов.

Лучше всего действовать так, как будто у вас есть факторы с вашей матрицей, чтобы принудить ее к характеру. Как только вы закончите работу, вы можете восстановить его обратно в факторную форму. Вы также можете сделать это с помощью целочисленной формы, но тогда вы рискуете странными вещами (например, вы можете умножить матрицу на целочисленную матрицу, но это не имеет смысла для факторов).

Обратите внимание, что если вы добавите "матрицу" класса к вашему коэффициенту, некоторые (но не все) вещи начнут работать:

f <- factor(letters[1:9])
dim(f) <- c(3, 3)
class(f) <- c("factor", "matrix")
head(f, 2)

Выдает:

     [,1] [,2] [,3]
[1,] a    d    g   
[2,] b    e    h   
Levels: a b c d e f g h i

Это не исправляет rbind и т.д.