Ответ 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 (который выведет уровни и другие атрибуты), чтобы вы могли хотя бы работать с объектами и посмотреть, где вам нужно добавить новые методы, чтобы заполнить поведение класса.