Почему метод извлечения для фреймов данных делает две копии? - PullRequest
0 голосов
/ 24 января 2019

Я пытаюсь лучше понять производительность для циклов в R. Я изменил пример из книги Хэдли здесь , но я все еще в замешательстве.

У меня есть следующая установка, где цикл for проходит через несколько случайных столбцов:

set.seed(123)
df <- as.data.frame(matrix(runif(1e3), ncol = 10))
cols <- sample(names(df), 2)
tracemem(df)

У меня есть цикл for, который выполняется для каждого элемента cols.

  for (i in seq_along(cols)) {
      df[[cols[i]]] <- 3.2
  }

Я получаю следующий список копий.

tracemem[0x1c54040 -> 0x20e1470]: 
tracemem[0x20e1470 -> 0x20e17b8]: [[<-.data.frame [[<- 
tracemem[0x20e17b8 -> 0x20dc4b8]: [[<-.data.frame [[<- 
tracemem[0x20dc4b8 -> 0x20dc800]: 
tracemem[0x20dc800 -> 0x20dc8a8]: [[<-.data.frame [[<- 
tracemem[0x20dc8a8 -> 0x20dcaa0]: [[<-.data.frame [[<- 

Хэдли отмечает в своем примере:

Фактически, каждая итерация копирует фрейм данных не один, не два раза, а три раза! Две копии сделаны [[.data.frame, и еще одна копия сделано потому, что [[.data.frame является обычной функцией, которая увеличивает счетчик ссылок х.

Может кто-нибудь объяснить , почему метод [[<-.data.frame должен делать две копии?

1 Ответ

0 голосов
/ 24 января 2019

Это не совсем полный ответ на ваш вопрос, но это начало.

Если вы посмотрите в определение языка R, вы увидите, что df[["name"]] <- 3.2 реализовано как

`*tmp*` <- df
df <- "[[<-.data.frame"(`*tmp*`, "name", value=3.2)
rm(`*tmp*`)

Таким образом, одна копия помещается в *tmp*. Если вы позвоните debug("[[<-.data.frame"), вы увидите, что он действительно вызывается с аргументом *tmp*, и tracemem() покажет, что первое дублирование произойдет до того, как вы введете.

Функция [[<-.data.frame является обычной функцией с таким заголовком:

function (x, i, j, value)  

Эта функция вызывается как

`[[<-.data.frame`(`*tmp*`, "name", value = 3.2)

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

Класс x изменяется в функции; это вызывает копию. Затем один из компонентов x изменяется; это еще одна копия. Так что получается 3.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...