быстрое подмножество в R - PullRequest
6 голосов
/ 20 января 2012

У меня есть датафрейм размером 30000 x 50. У меня также есть отдельный список, который содержит точки на группы строк из этого фрейма, например,

rows <- list(c("34", "36", "39"), c("45", "46"))

Это говорит о том, что строки данных с именами строк (не числовые индексы строк, а имена строк символов (dat)) «34», «36», «39» составляют одну группу, а «45», «46» составляют другую группу.

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

> system.time(lapply(rows, function(r) {dat[r, ]}))
   user  system elapsed 
 246.09    0.01  247.23 

Это на очень быстром компьютере, R 2.14.1 x64.

Ответы [ 5 ]

17 голосов
/ 20 января 2012

Одной из основных проблем является сопоставление имен строк - по умолчанию в [.data.frame используется частичное сопоставление имен строк, и вы, вероятно, этого не хотите, поэтому вам лучше использовать match. Чтобы ускорить его еще дальше, вы можете использовать fmatch из fastmatch, если хотите. Это небольшая модификация с некоторым ускорением:

# naive
> system.time(res1 <- lapply(rows,function(r) dat[r,]))
   user  system elapsed 
 69.207   5.545  74.787 

# match
> rn <- rownames(dat)
> system.time(res1 <- lapply(rows,function(r) dat[match(r,rn),]))
   user  system elapsed 
 36.810  10.003  47.082 

# fastmatch
> rn <- rownames(dat)
> system.time(res1 <- lapply(rows,function(r) dat[fmatch(r,rn),]))
   user  system elapsed 
 19.145   3.012  22.226 

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

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

5 голосов
/ 20 января 2012

Обновление

Мой оригинальный пост начинался с этого ошибочного утверждения:

Проблема с индексацией через rownames и colnames заключается в том, что вы выполняете векторное / линейное сканированиедля каждого элемента, например.Вы просматриваете каждую строку, чтобы увидеть, что называется «36», а затем начинаете с начала, чтобы сделать это снова для «34».

В комментариях Саймон указал, что R, очевидно, используетхеш-таблица для индексации.Извините за ошибку.

Оригинальный ответ

Обратите внимание, что предложения в этом ответе предполагают, что у вас есть непересекающиеся подмножества данных.

ЕслиВы хотите сохранить свою стратегию поиска в списке, я бы рекомендовал хранить фактические индексы строк вместо имен строк.

Альтернативой является сохранение информации о вашей "группе" в качестве другого столбца для вашего data.frame,затем split ваш data.frame в своей группе, например.скажем, ваш перекодированный data.frame выглядит следующим образом:

dat <- data.frame(a=sample(100, 10),
                  b=rnorm(10),
                  group=sample(c('a', 'b', 'c'), 10, replace=TRUE))

Затем вы можете сделать:

split(dat, dat$group)
$a
   a           b group
2 66 -0.08721261     a
9 62 -1.34114792     a

$b
    a          b group
1  32  0.9719442     b
5  79 -1.0204179     b
6  83 -1.7645829     b
7  73  0.4261097     b
10 44 -0.1160913     b

$c
   a          b group
3 77  0.2313654     c
4 74 -0.8637770     c
8 29  1.0046095     c

Или, в зависимости от того, что вы действительно хотите делать со своими "сплитами",Вы можете преобразовать data.frame в data.table и установить его ключ для вашего нового столбца group:

library(data.table)
dat <- data.table(dat, key="group")

Теперь сделайте ваш список - что даст вамтот же результат, что и split выше

 x <- lapply(unique(dat$group), function(g) dat[J(g),])

Но вы, вероятно, хотите «работать над своими плевками», и вы можете сделать это встроенным, например:

ans <- dat[, {
  ## do some code over the data in each split
  ## and return a list of results, eg:
  list(nrow=length(a), mean.a=mean(a), mean.b=mean(b))
}, by="group"]

ans
     group nrow mean.a     mean.b
[1,]     a    2   64.0 -0.7141803
[2,]     b    5   62.2 -0.3006076
[3,]     c    3   60.0  0.1240660

Вы можетевыполните последний шаг "аналогичным образом" с plyr, например:

library(plyr)
ddply(dat, "group", summarize, nrow=length(a), mean.a=mean(a),
      mean.b=mean(b))
  group nrow mean.a     mean.b
1     a    2   64.0 -0.7141803
2     b    5   62.2 -0.3006076
3     c    3   60.0  0.1240660

Но поскольку вы упоминаете, что ваш набор данных довольно большой, я думаю, вам понравится скоростьУсиление data.table обеспечит.

4 голосов
/ 20 января 2012

Вот одна из попыток ускорения - он зависит от того, что поиск индекса строки быстрее, чем поиск имени строки, и поэтому пытается сопоставить имя строки с номером строки в dat.

Сначала создайте некоторые данные того же размера, что и ваши, и присвойте несколько числовых имен строк:

> dat <- data.frame(matrix(runif(30000*50),ncol=50))
> rownames(dat) <- as.character(sample.int(nrow(dat)))
> rownames(dat)[1:5]
[1] "21889" "3050"  "22570" "28140" "9576" 

Теперь сгенерируйте случайное значение rows с 15000 элементов, каждое из 50 случайных чисел от 1 до 30000(в данном случае это строка * names *):

# 15000 groups of up to 50 rows each
> rows <- sapply(1:15000, function(i) as.character(sample.int(30000,size=sample.int(50,size=1))))

В целях определения времени, попробуйте метод в вашем вопросе ( ой! ):

# method 1
> system.time((res1 <- lapply(rows,function(r) dat[r,])))
   user  system elapsed 
182.306   0.877 188.362 

Теперь попробуйте сделать сопоставление от имени строки к номеру строки.map[i] должен дать номер строки с именем i.

FIRST , если ваша строка имена являются перестановкой 1:nrow(dat), вам повезло!Все, что вам нужно сделать, это отсортировать имена строк и вернуть индексы:

> map <- sort(as.numeric(rownames(dat)), index.return=T)$ix
# NOTE: map[ as.numeric(rowname) ] -> rownumber into dat for that rowname.

Теперь ищите индексы строк вместо имен строк:

> system.time((res2 <- lapply(rows,function(r) dat[map[as.numeric(r)],])))
   user  system elapsed
 32.424   0.060  33.050

Убедитесь, что мы ничего не привинтиливверх (обратите внимание, что достаточно совпадать с именами строк, поскольку имена строк уникальны в R):

> all(rownames(res1)==rownames(res2))
[1] TRUE

Итак, ускорение ~ в 6 раз.Хотя все еще не удивительно ...

SECOND Если вам не повезло и ваши имена строк вообще не связаны с nrow(dat), вы могли бы попробовать это, , но только если max(as.numeric(rownames(dat))) не намного больше, чем nrow(dat).В основном это делает map с map[rowname], дающим номер строки, но поскольку имена строк больше не обязательно являются непрерывными, в map могут быть кучи пробелов, которые тратят немного памяти:

map <- rep(-1,max(as.numeric(rownames(dat))))
obj <- sort(as.numeric(rownames(dat)), index.return=T)
map[obj$x] <- obj$ix

Затем используйте map как раньше (dat[map[as.numeric(r),]]).

2 голосов
/ 20 января 2012

Вы можете попробовать эту модификацию:

system.time(lapply(rows, function(r) {dat[ rownames(dat) %in% r, ]}))
1 голос
/ 20 января 2012

Я согласен с математическим кофе, что я тоже получаю быстрые времена для этого.

Не знаю, возможно ли это, но, если в качестве вектора добавить его в список, а затем преобразовать в числовое значение, можно получить повышение скорости.

dat <- data.frame(matrix(rnorm(30000*50), 30000, 50 ))
rows <- as.numeric(unlist(list(c("34", "36", "39"), c("45", "46"))))
system.time(lapply(rows, function(r) {dat[r, ]}))

РЕДАКТИРОВАТЬ:

dat$observ <- rownames(dat)
rownames(dat) <- 1:nrow(dat)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...