Ломтики таблицы занимают память в R? - PullRequest
8 голосов
/ 17 марта 2011

Если я возьму фрагмент таблицы, используя, скажем, имена столбцов, выделяет ли R память для хранения фрагмента в новом месте? В частности, у меня есть таблица со столбцами глубиной1 и глубиной2, среди других. Я хочу добавить столбцы, которые содержат максимум и минимум из двух. У меня есть 2 подхода:

dd = dat[,c("depth1","depth2")]
dat$mindepth = apply(dd,1,min)
dat$maxdepth = apply(dd,1,max)
remove(dd)

или

dat$mindepth = apply(dat[,c("depth1","depth2")],1,min)
dat$maxdepth = apply(dat[,c("depth1","depth2")],1,max)

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

1 Ответ

6 голосов
/ 01 апреля 2011

Я знаю, что это не на самом деле отвечает на основной вопрос (@hadley сделал это и заслуживает похвалы), но есть и другие варианты, которые вы предлагаете.Здесь вы можете использовать pmin() и pmax() в качестве другого решения, и используя with() или within(), мы можем сделать это без явного поднабора для создания dd.

R> set.seed(1)
R> dat <- data.frame(depth1 = runif(10), depth2 = runif(10))
R> dat <- within(dat, mindepth <- pmin(depth1, depth2))
R> dat <- within(dat, maxdepth <- pmax(depth1, depth2))
R> 
R> dat
       depth1    depth2   mindepth  maxdepth
1  0.26550866 0.2059746 0.20597457 0.2655087
2  0.37212390 0.1765568 0.17655675 0.3721239
3  0.57285336 0.6870228 0.57285336 0.6870228
4  0.90820779 0.3841037 0.38410372 0.9082078
5  0.20168193 0.7698414 0.20168193 0.7698414
6  0.89838968 0.4976992 0.49769924 0.8983897
7  0.94467527 0.7176185 0.71761851 0.9446753
8  0.66079779 0.9919061 0.66079779 0.9919061
9  0.62911404 0.3800352 0.38003518 0.6291140
10 0.06178627 0.7774452 0.06178627 0.7774452

Мы можем посмотретьна сколько копирование продолжается с tracemem(), но только , если ваш R скомпилирован со следующей активированной опцией конфигурации --enable-memory-profiling.

R> set.seed(1)
R> dat <- data.frame(depth1 = runif(10), depth2 = runif(10))
R> tracemem(dat)
[1] "<0x2641cd8>"
R> dat <- within(dat, mindepth <- pmin(depth1, depth2))
tracemem[0x2641cd8 -> 0x2641a00]: within.data.frame within 
tracemem[0x2641a00 -> 0x2641878]: [<-.data.frame [<- within.data.frame within 
R> tracemem(dat)
[1] "<0x2657bc8>"
R> dat <- within(dat, maxdepth <- pmax(depth1, depth2))
tracemem[0x2657bc8 -> 0x2c765d8]: within.data.frame within 
tracemem[0x2c765d8 -> 0x2c764b8]: [<-.data.frame [<- within.data.frame within

Итак, мы видим, что R скопировано dat дважды за каждый within() звонок.Сравните это с вашими двумя предложениями:

R> set.seed(1)
R> dat <- data.frame(depth1 = runif(10), depth2 = runif(10))
R> tracemem(dat)
[1] "<0x2e1ddd0>"
R> dd <- dat[,c("depth1","depth2")]
R> tracemem(dd)
[1] "<0x2df01a0>"
R> dat$mindepth = apply(dd,1,min)
tracemem[0x2df01a0 -> 0x2cf97d8]: as.matrix.data.frame as.matrix apply 
tracemem[0x2e1ddd0 -> 0x2cc0ab0]: 
tracemem[0x2cc0ab0 -> 0x2cc0b20]: $<-.data.frame $<- 
tracemem[0x2cc0b20 -> 0x2cc0bc8]: $<-.data.frame $<- 
R> tracemem(dat)
[1] "<0x26b93c8>"
R> dat$maxdepth = apply(dd,1,max)
tracemem[0x2df01a0 -> 0x2cc0e30]: as.matrix.data.frame as.matrix apply 
tracemem[0x26b93c8 -> 0x26742c8]: 
tracemem[0x26742c8 -> 0x2674358]: $<-.data.frame $<- 
tracemem[0x2674358 -> 0x2674478]: $<-.data.frame $<-

Здесь dd копируется один раз за каждый вызов в apply, потому что apply() преобразует dd в матрицу перед продолжением.Последние три строки в каждом блоке вывода tracemem указывают на то, что для вставки нового столбца делается три копии dat.

А как насчет вашего второго варианта?

R> set.seed(1)
R> dat <- data.frame(depth1 = runif(10), depth2 = runif(10))
R> tracemem(dat)
[1] "<0x268bc88>"
R> dat$mindepth <- apply(dat[,c("depth1","depth2")],1,min)
tracemem[0x268bc88 -> 0x26376b0]: 
tracemem[0x26376b0 -> 0x2637720]: $<-.data.frame $<- 
tracemem[0x2637720 -> 0x2637790]: $<-.data.frame $<- 
R> tracemem(dat)
[1] "<0x2466d40>"
R> dat$maxdepth <- apply(dat[,c("depth1","depth2")],1,max)
tracemem[0x2466d40 -> 0x22ae0d8]: 
tracemem[0x22ae0d8 -> 0x22ae1f8]: $<-.data.frame $<- 
tracemem[0x22ae1f8 -> 0x22ae318]: $<-.data.frame $<-

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

Можем ли мы сделать что-нибудь лучше?Да, и один простой способ - использовать опцию within(), с которой я начал, но выполнить оба оператора для создания новых переменных mindepth и maxdepth в одном вызове within():

R> set.seed(1)
R> dat <- data.frame(depth1 = runif(10), depth2 = runif(10))
R> tracemem(dat)
[1] "<0x21c4158>"
R> dat <- within(dat, { mindepth <- pmin(depth1, depth2)
+                      maxdepth <- pmax(depth1, depth2) })
tracemem[0x21c4158 -> 0x21c44a0]: within.data.frame within 
tracemem[0x21c44a0 -> 0x21c4628]: [<-.data.frame [<- within.data.frame within

В этой версии мы вызываем только две копии dat по сравнению с 4 копиями оригинальной within() версии.

Как насчет того, чтобы принудительно dat привести к матрице и затем выполнить вставки?

R> set.seed(1)
R> dat <- data.frame(depth1 = runif(10), depth2 = runif(10))
R> tracemem(dat)
[1] "<0x1f29c70>"
R> mat <- as.matrix.data.frame(dat)
tracemem[0x1f29c70 -> 0x1f09768]: as.matrix.data.frame 
R> tracemem(mat)
[1] "<0x245ff30>"
R> mat <- cbind(mat, pmin(mat[,1], mat[,2]), pmax(mat[,1], mat[,2]))
R>

Это улучшение, поскольку мы берем на себя стоимость единственной копии dat только при приведении к матрице.Я немного обманул, вызвав метод as.matrix.data.frame() напрямую.Если бы мы только что использовали as.matrix(), мы бы получили еще одну копию mat.

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

...