Оптимизирайте прекодирането в база R

Аз прекодирам променлива, базирана на някои доста дълги низове, тук илюстрирани от низовете A, B, C, D, E и G. Чудех се дали има начин да прекодирам това, без да се налага да повтарям препратката към df$foo 12 пъти с база R? Може би има някакъв по-интелигентен по-бърз начин, който мога да проуча? това наистина ли е най-умният начин да го направите в R?

df <- data.frame(
  foo = 1000:1010,
  bar = letters[1:11])
df  
    foo bar
1  1000   a
2  1001   b
3  1002   c
4  1003   d
5  1004   e
6  1005   f
7  1006   g
8  1007   h
9  1008   i
10 1009   j
11 1010   k

A  <- c(1002)
B  <- c(1007, 1008)
C  <- c(1001, 1003)
D  <- c(1004, 1006)
E  <- c(1000, 1005)
G  <- c(1010, 1009)

df$foo[df$foo %in% A] <- 1
df$foo[df$foo %in% B] <- 2
df$foo[df$foo %in% C] <- 3
df$foo[df$foo %in% D] <- 4
df$foo[df$foo %in% E] <- 5
df$foo[df$foo %in% G] <- 7
df
   foo bar
1    5   a
2    3   b
3    1   c
4    3   d
5    4   e
6    5   f
7    4   g
8    2   h
9    2   i
10   7   j
11   7   k

Актуализация на 2013-03-11 05:28:061Z,

Пренаписах петте решения на функции, за да мога да ги сравня с помощта на пакета за микробенчмарк и резултатът е, че решенията на Tyler Rinker и flodel са най-бързите решения (вижте резултатите по-долу), да не казвам, че този въпрос е само за скоростта. Също така търся сбитост и умност в решението. От любопитство добавих и решение с помощта на функцията Recode от пакета на автомобила. Моля, не се колебайте да ме уведомите дали бих могъл да пренапиша решенията по по-оптимален начин или пакетът за микробенчмарк не е най-добрият начин за сравняване на тези функции.

df <- data.frame(
  foo = sample(1000:1010, 1e5+22, replace = TRUE),
  bar = rep(letters, 3847))
str(df)

A  <- c(1002)
B  <- c(1007, 1008)
C  <- c(1001, 1003)
D  <- c(1004, 1006)
E  <- c(1000, 1005)
G  <- c(1010, 1009)

# juba's solution
juba <- function(df,foo) within(df, {foo[foo %in% A] <- 1; foo[foo %in% B] <- 2;foo[foo %in% C] <- 3;foo[foo %in% D] <- 4;foo[foo %in% E] <- 5;foo[foo %in% G] <- 7})
# Arun's solution
Arun <- function(df,x) factor(df[,x], levels=c(A,B,C,D,E,G), labels=c(1, rep(c(2:5, 7), each=2)))
# flodel's solution
flodel <- function(df,x) rep(c(1, 2, 3, 4, 5, 7), sapply(list(A, B, C, D, E, G), length))[match(df[,x], unlist(list(A, B, C, D, E, G)))]
# Tyler Rinker's solution
TylerRinker <- function(df,x)  data.frame(vals = unlist(list(A  = c(1002),B  = c(1007, 1008),C  = c(1001, 1003),D  = c(1004, 1006),E  = c(1000, 1005), G = c(1010, 1009))), labs = c(1, rep(c(2:5, 7), each=2)))[match(df[,x], unlist(list(A  = c(1002),B  = c(1007, 1008),C  = c(1001, 1003),D  = c(1004, 1006),E  = c(1000, 1005), G = c(1010, 1009)))), 2] 
# agstudy's solution
agstudy <- function(df,foo) merge(df,data.frame(foo=unlist(list(A, B, C, D, E, G)), val =rep((1:7)[-6],rapply(list(A, B, C, D, E, G), length))))
# Recode from the car package
ReINcar <- function(df,x) Recode(df[,x], "A='A'; B='B'; C='C'; D='D'; E='E'; G='G'")

# install.packages("microbenchmark", dependencies = TRUE)
require(microbenchmark)

# run test
res <- microbenchmark(juba(df, foo), Arun(df, 1), flodel(df, 1), TylerRinker(df,1) ,agstudy(df, foo), ReINcar(df, 1), times = 25)
There were 15 warnings (use warnings() to see them) # warning duo to x's solution

## Print results:
print(res)

числата,

   Unit: milliseconds
                   expr        min         lq     median         uq        max neval
          juba(df, foo)  37.944355  39.521603  41.987174  46.385974  79.559750    25
            Arun(df, 1)  23.833334  24.115776  24.648842  26.987431  55.466448    25
          flodel(df, 1)   3.586179   3.637024   3.956814   6.468735  28.404166    25
     TylerRinker(df, 1)   3.919563   4.115994   4.529926   5.532688   8.508956    25
       agstudy(df, foo) 301.487732 324.641734 334.801005 352.753496 415.421212    25
         ReINcar(df, 1)  73.655566  77.903088  81.745037 101.038791 125.158208    25


### Plot results:
boxplot(res)

Box Plot на резултатите от микробенчмарка,

Боксов график на резултатите от микробенчмарк


person Eric Fail    schedule 09.03.2013    source източник
comment
Има дублирани стойности в A и B. Това правилно ли е?   -  person Arun    schedule 10.03.2013
comment
@Arun, не. Това е правописна грешка от моя страна. Актуализирах въпроса си. Благодаря!   -  person Eric Fail    schedule 10.03.2013
comment
Можете също така да разгледате функциите recode от пакетите memisc и car.   -  person juba    schedule 10.03.2013


Отговори (6)


Ето общ (мащабируем) подход, също много бърз:

sets <- list(A, B, C, D, E, G)
vals <-    c(1, 2, 3, 4, 5, 7)

keys   <- unlist(sets)
values <- rep(vals, sapply(sets, length))
df$foo <- values[match(df$foo, keys)]
person flodel    schedule 10.03.2013
comment
Актуализирах въпроса си с груб сравнителен тест, където сравнявам вашето решение с четирите други решения за скорост. Моля, не се колебайте да коригирате функцията, която съм написал, използвана за представяне на вашето решение в теста. - person Eric Fail; 11.03.2013

Използването на within може да ви помогне да спестите някои натискания на клавиши:

df <- within(df,
       {foo[foo %in% A] <- 1;
        foo[foo %in% B] <- 2;
        foo[foo %in% C] <- 3;
        foo[foo %in% D] <- 4;
        foo[foo %in% E] <- 5;
        foo[foo %in% G] <- 7})
person juba    schedule 09.03.2013
comment
Благодаря ви за отговора. Харесва ми, че успяхте да премахнете 12 * df$, но все още донякъде се повтаря на 'foo'. Could you explain the use of ;`? - person Eric Fail; 10.03.2013
comment
@EricFail within приема израз като втори аргумент. Тук искаме да изпълним няколко израза, така че ги предавам на within, оградени в скоби { и разделени с ;. Това е същият начин, по който можете да предадете няколко израза в един ред на R. - person juba; 10.03.2013
comment
Може да е проблем с операционната система, но на моята машина (*NIX) кодът работи перфектно без ;. Имам предвид, че имате своя код на отделни редове. - person Eric Fail; 10.03.2013
comment
@EricFail Упс, съжалявам, напълно си прав, работи без ;. Те са необходими само ако изразите са на един ред. Благодаря, че посочихте това! - person juba; 10.03.2013
comment
Актуализирах въпроса си с груб сравнителен тест, където сравнявам вашето решение с четирите други решения за скорост. Моля, не се колебайте да коригирате функцията, която съм написал, използвана за представяне на вашето решение в теста. - person Eric Fail; 11.03.2013

Можете също да направите: (Редактирано)

> df$foo <- factor(df$foo, levels=c(A,B,C,D,E,G), labels=c(1, rep(c(2:5, 7), each=2)))

# Warning message:
# In `levels<-`(`*tmp*`, value = if (nl == nL) as.character(labels) else paste0(labels,  :
#   duplicated levels will not be allowed in factors anymore

#    foo bar
# 1    5   a
# 2    3   b
# 3    1   c
# 4    3   d
# 5    4   e
# 6    5   f
# 7    4   g
# 8    2   h
# 9    2   i
# 10   7   j
# 11   7   k
person Arun    schedule 09.03.2013
comment
Благодаря ви, че отговорихте на въпроса ми. Актуализирах въпроса си и сега получавам различна грешка (по отношение на дължината на етикета). Намирам вашето решение за много интересно! - person Eric Fail; 10.03.2013
comment
Редактирах решението за новите данни. Изглежда, че предупреждението продължава да съществува и е така, защото нивата нямат уникални етикети. Но все още връща правилния резултат. - person Arun; 10.03.2013
comment
Благодаря, но трябва да призная, че предупреждението ме изнервя малко. - person Eric Fail; 10.03.2013
comment
Ако след това направите още веднъж: df$foo <- factor(df$foo), нивата на факторите се възстановяват. Така че не мисля, че е проблем. Ще го оставя на вас. - person Arun; 10.03.2013
comment
Актуализирах въпроса си с груб сравнителен тест, където сравнявам вашето решение с четирите други решения за скорост. Моля, не се колебайте да коригирате функцията, която съм написал, използвана за представяне на вашето решение в теста. - person Eric Fail; 11.03.2013

Моят подход (загубете A, B, C... всички заедно, но виждам, че този на flodel е много подобен).

keyL <- list(
    A  = c(1002),
    B  = c(1007, 1008),
    C  = c(1001, 1003),
    D  = c(1004, 1006),
    E  = c(1000, 1005),
    G  = c(1010, 1009)
)

key <- data.frame(vals = unlist(keyL), labs = c(1, rep(c(2:5, 7), each=2)))

df$foo2 <- key[match(df$foo, key$vals), 2] 

Не обичам да пиша върху стари колони, затова създадох нова. Също така бих съхранил ключа като именуван списък.

person Tyler Rinker    schedule 10.03.2013
comment
Актуализирах въпроса си с груб сравнителен тест, където сравнявам вашето решение с четирите други решения за скорост. Моля, не се колебайте да коригирате функцията, която съм написал, използвана за представяне на вашето решение в теста. - person Eric Fail; 11.03.2013

Друг вариант е да използвате merge, много подобен на @flodel и @Tyler подход

sets <- list(A, B, C, D, E, G)
df.code = data.frame(foo=unlist(sets),
                     val =rep((1:7)[-6],rapply(sets, length)))
> merge(df,df.code)
    foo bar val
1  1000   a   5
2  1001   b   3
3  1002   c   1
4  1003   d   3
5  1004   e   4
6  1005   f   5
7  1006   g   4
8  1007   h   2
9  1008   i   2
10 1009   j   7
11 1010   k   7 
person agstudy    schedule 10.03.2013
comment
Актуализирах въпроса си с груб сравнителен тест, където сравнявам вашето решение с четирите други решения за скорост. Моля, не се колебайте да коригирате функцията, която съм написал, използвана за представяне на вашето решение в теста. - person Eric Fail; 11.03.2013

Мисля, че това прави това, което искате, макар и с малко по-различен формат. И това вероятно ще бъде най-бързият метод.

library(data.table)

## Create the sample data:
dt <- data.table(foo=sample(1000:1010, 1e5+22, replace = TRUE), bar=rep(letters, 3847), key="foo")

## Create the table that maps the old value of foo to the new one:
dt.recode<-data.table(foo_old=1000:1010, foo_new=c(5L, 3L, 1L, 3L, 4L, 5L, 4L, 2L, 2L, 7L, 7L), key="foo_old")

## Show the result of the join/merge between the original and recoded table:
## (not necesary if you only want to update the original table)
dt[dt.recode]
##       foo bar foo_new
##  1: 1000   a       5
##  2: 1001   b       3
##  3: 1002   c       1
##  4: 1003   d       3
##  5: 1004   e       4
##  6: 1005   f       5
##  7: 1006   g       4
##  8: 1007   h       2
##  9: 1008   i       2
## 10: 1009   j       7
## 11: 1010   k       7

## Same as above, but updates the value of foo in the original table:
dt[dt.recode,foo:=foo_new][]
##     foo bar
##  1:   5   a
##  2:   3   b
##  3:   1   c
##  4:   3   d
##  5:   4   e
##  6:   5   f
##  7:   4   g
##  8:   2   h
##  9:   2   i
## 10:   7   j
## 11:   7   k

Ето как да преобразувате вашата рамка с данни в таблица с данни (и да добавите ключа, който е необходим за свързването по-късно), ако предпочитате това, вместо да създавате таблицата с данни от нулата:

dt <- as.data.table(df)
setkey(dt,foo)

Не съм сигурен как искате да преброите времето с този метод, но ако приемем, че dt и dt.recode вече съществуват и са въведени, след това изпълнението на единствения ред, който актуализира таблицата, показа 0 изминало време на моята система.

Освен това, ако вашите групи A, B, C, D, E, G имат някакво присъщо значение, бих ги добавил като колона към вашата оригинална таблица. След това можете да се присъедините към това поле и dt.recode ще се нуждае само от 6 реда (ако приемем, че имате шест групи).

person dnlbrky    schedule 22.06.2013
comment
Благодаря за отговора, аз обаче вече използвах отговора на flodel, който той публикува на 10 март. Независимо от това, оценявам вашия вход! - person Eric Fail; 26.06.2013