2012-03-20 3 views
3

Я имею дело с категориальной переменной, полученной из базы данных, и я хочу использовать факторы для поддержания «полноты» данных.Уровни хэша или списка факторов

Например, у меня есть таблица, которая хранит цвета и связанные с ними числовой идентификатор

 
    ID | Color 
------+------- 
    1 | Black 
1805 | Red 
3704 | White 

Так что я хотел бы использовать фактор для хранения этой информации в кадре данных, таких как:

 
Car Model | Color 
----------+------- 
Civic  | Black 
Accord | White 
Sentra | Red 

где цветная колонка является фактором, а базовые данные, хранящиеся, а не как строка, на самом деле являются c (1, 3704, 1805) - идентификаторами, связанными с каждым цветом.

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

К сожалению, как вы можете видеть в примере, мои идентификаторы не увеличиваются. В моем приложении у меня есть 30 уровней, а максимальный ID для одного уровня - ~ 9 000. Поскольку уровни хранятся в массиве для множителя, это означает, что я храню целочисленный вектор длиной 9000 с только 30 элементами в нем.

Можно ли использовать хэш или список для более эффективного выполнения этого эффекта? то есть, если бы я использовал хэш в атрибуте уровней фактора, я мог бы хранить все 30 элементов с любыми индексами, которые мне нравятся, без создания массива размера max (ID).

Заранее благодарен!

ответ

2

Ну, я уверен, что вы не можете изменить, как работают факторы. У фактора всегда есть идентификаторы уровня, которые являются целыми числами 1..n, где n - количество уровней.

... но вы можете легко иметь вектор перемещения, чтобы добраться до ваших цветовых идентификаторов:

# The translation vector... 
colorIds <- c(Black=1,Red=1805,White=3704) 

# Create a factor with the correct levels 
# (but with level ids that are 1,2,3...) 
f <- factor(c('Red','Black','Red','White'), levels=names(colorIds)) 
as.integer(f) # 2 1 2 3 

# Translate level ids to your color ids 
colorIds[f] # 1805 1 1805 3704 

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

EDIT Однако можно создать класс, основанный на коэффициенте, который имеет коды как атрибут. Назовём этот новый славный класс foo:

foo <- function(x = character(), levels, codes) { 
    f <- factor(x, levels) 
    attr(f, 'codes') <- codes 
    class(f) <- c('foo', class(f)) 
    f 
} 

`[.foo` <- function(x, ...) { 
    y <- NextMethod('[') 
    attr(y, 'codes') <- attr(x, 'codes') 
    y 
} 

as.integer.foo <- function(x, ...) attr(x,'codes')[unclass(x)] 

# Try it out 
set.seed(42) 
f <- foo(sample(LETTERS[1:5], 10, replace=TRUE), levels=LETTERS[1:5], codes=101:105) 

d <- data.frame(i=11:15, f=f) 

# Try subsetting it... 
d2 <- d[2:5,] 

# Gets the codes, not the level ids... 
as.integer(d2$f) # 105 102 105 104 

Вы могли бы также исправить print.foo и т.д ...

+0

Спасибо, Томми. Я надеюсь на что-то, что не позволит сделать второй шаг (поиск фактического идентификатора), но то, что вы предложили, может оказаться таким же хорошим, как и получается. –

+0

... Я обновил ответ одним возможным решением ... – Tommy

+0

Это решение кажется немного менее изящным для меня, чем другой хэш-ответ (хотя, вероятно, проще работать), но - удивительно - я получая лучшую производительность с этим кодом, чем я использовал хэш. Похоже, хотя и постоянное время для извлечения элемента из хешированной среды достаточно долго, чтобы ваш код на основе массива работал лучше, когда количество уровней <~ 4000 (хеш принимает константу .25s для форматирования строки в 10 000 строк df , ваш занимает .17 + .0000212L где L - количество уровней). –

0

Размышляя об этом, единственная особенность, что «уровень» необходимо реализовать для того, чтобы иметь действительным фактором является аксессуар [. Таким образом, любой объект, реализующий аксессуар [, можно рассматривать как вектор с точки зрения любой функции сопряжения.

Я просмотрел класс hash, но увидел, что он использует обычное поведение R (как видно из списков), возвращающее срез исходного хеша, когда используется только одна скобка (при извлечении фактического значения при использовании двойной кронштейн). Однако я должен был переопределить это с помощью setMethod(), я действительно смог получить желаемое поведение.

library(hash) 

setMethod( 
    '[' , 
    signature(x="hash", i="ANY", j="missing", drop = "missing") , 
    function( 
     x,i,j, ... ,   
     drop 
     ) {  

     if (class(i) == "factor"){ 
      #presumably trying to lookup the values associated with the ordered keys in this hash 
      toReturn <- NULL 
      for (k in make.keys(as.integer(i))){ 
       toReturn <- c(toReturn, get(k, [email protected])) 
      } 
      return(toReturn) 
     } 

     #default, just make keys and get from the environment 
     toReturn <- NULL 
     for (k in make.keys(i)){ 
      toReturn <- c(toReturn, get(k, [email protected])) 
     } 
     return(toReturn)   
    } 
    ) 

as.character.hash <- function(h){ 
    as.character(values(h)) 
} 

print.hash <- function(h){ 
    print(as.character(h)) 
} 

h <- hash(1:26, letters) 

df <- data.frame(ID=1:26, letter=26:1, stringsAsFactors=FALSE) 

attributes(df$letter)$class <- "factor" 
attributes(df$letter)$levels <- h 

> df 
    ID letter 
1 1  z 
2 2  y 
3 3  x 
4 4  w 
5 5  v 
6 6  u 
7 7  t 
8 8  s 
9 9  r 
10 10  q 
11 11  p 
12 12  o 
13 13  n 
14 14  m 
15 15  l 
16 16  k 
17 17  j 
18 18  i 
19 19  h 
20 20  g 
21 21  f 
22 22  e 
23 23  d 
24 24  c 
25 25  b 
26 26  a 
> attributes(df$letter)$levels 
<hash> containing 26 key-value pair(s). 
    1 : a 
    10 : j 
    11 : k 
    12 : l 
    13 : m 
    14 : n 
    15 : o 
    16 : p 
    17 : q 
    18 : r 
    19 : s 
    2 : b 
    20 : t 
    21 : u 
    22 : v 
    23 : w 
    24 : x 
    25 : y 
    26 : z 
    3 : c 
    4 : d 
    5 : e 
    6 : f 
    7 : g 
    8 : h 
    9 : i 
> 
> df[1,2] 
[1] z 
Levels: a j k l m n o p q r s b t u v w x y z c d e f g h i 
> as.integer(df$letter) 
[1] 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 
[26] 1 

Любые отзывы об этом? Насколько я могу судить, все работает. Похоже, что он работает правильно, насколько печатается, а базовые данные, хранящиеся в фактическом data.frame, не тронуты, поэтому я не чувствую, что меня что-то угрожает. Я даже могу уйти с добавлением нового класса в свой пакет, который просто реализует этот аксессуар, чтобы избежать необходимости добавлять зависимость от хэш-класса.

Любая обратная связь или мнения о том, что я пропускаю, будут высоко оценены.

Смежные вопросы