Какова цель установки ключа в data.table?

Я использую data.table, и есть много функций, которые требуют от меня установить ключ (например, X[Y]). Таким образом, я хочу понять, что делает ключ, чтобы правильно устанавливать ключи в моих таблицах данных.


Один источник, который я прочитал, был ?setkey.

setkey() сортирует a data.table и отмечает его как отсортированный. Сортированные столбцы - это ключ. Ключ может быть любым столбцом в любом порядке. Колонки сортируются в порядке возрастания всегда. Таблица изменяется по ссылке. Никакой копии не делается вообще, кроме временной рабочей памяти размером не более одного столбца.

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


В Таблицах данных 3.2 и 3.3 объясняется:

3.2 У меня нет ключа на большой таблице, но группировка по-прежнему очень быстрая. Почему это?

data.table использует сортировку radix. Это значительно быстрее, чем другие алгоритмы сортировки. Radix специально предназначен для целых чисел, см. ?base::sort.list(x,method="radix"). Это также одна из причин, почему setkey() Быстро. Когда ключ не установлен, или мы группируем в другом порядке от ключа, мы называем его ad hoc by.

3.3 Почему группировка по столбцам в ключе быстрее, чем ad hoc?

Поскольку каждая группа смежна в ОЗУ, тем самым сводя к минимуму страницу выборки и память могут быть скопированы навалом (memcpy в C), а не цикл в C.

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


10-минутное руководство по быстрому запуску также содержит руководство по клавишам.

  • Клавиши

Давайте начнем с рассмотрения data.frame, specically rownames (или в Английский, имена строк). То есть, несколько имен, принадлежащих одному ряд. Несколько имен, принадлежащих к одной строке? Это не то, что мы привыкли в data.frame. Мы знаем, что каждая строка имеет не более одного имя. У человека есть как минимум два имени, первое имя и второе имя. Это полезно для организации телефонного справочника, например, который сортируется по фамилии, затем первому имени. Однако каждая строка в data.frame может иметь только одно имя.

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

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

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

Ответ 1

Незначительное обновление: см. новые HTML-виньетки. Этот вопрос освещает другие виньетки, которые мы планируем.


Я снова обновил этот ответ (февраль 2016) в свете новой функции on=, которая также позволяет использовать ad-hoc. См. Историю предыдущих (устаревших) ответов.

Что именно делает setkey(DT, a, b)?

Он выполняет две функции:

  • переупорядочивает строки data.table DT по столбцу (а), предоставленному (a, b) по ссылке, всегда в порядке возрастания.
  • помещает эти столбцы в качестве ключевых столбцов, установив атрибут с именем sorted в DT.

Переупорядочение является быстрым (из-за внутренней сортировки по индексу data.table) и эффективной памяти (выделяется только один дополнительный столбец типа double).

Когда требуется setkey()?

Для операций группировки setkey() никогда не было абсолютным требованием. То есть, мы можем выполнять простуду или adhoc-by.

## "cold" by
require(data.table)
DT <- data.table(x=rep(1:5, each=2), y=1:10)
DT[, mean(y), by=x] # no key is set, order of groups preserved in result

Однако, до v1.9.6, объединения формы x[i] требовали установки key на x. С новым аргументом on= от v1.9.6 + это больше не соответствует действительности, поэтому установка ключей также не является абсолютным требованием.

## joins using < v1.9.6 
setkey(X, a) # absolutely required
setkey(Y, a) # not absolutely required as long as 'a' is the first column
X[Y]

## joins using v1.9.6+
X[Y, on="a"]
# or if the column names are x_a and y_a respectively
X[Y, on=c("x_a" = "y_a")]

Обратите внимание, что аргумент on= может быть явно указан даже для соединений keyed.

Единственной операцией, которая требует, чтобы key была абсолютно установлена, является функция foverlaps(). Но мы работаем над некоторыми дополнительными функциями, которые, когда это будет сделано, устранили бы это требование.

  • Итак, в чем причина реализации аргумента on=?

    Существует немало причин.

    • Это позволяет четко различать операцию как операцию, включающую два data.tables. Просто выполнение X[Y] также не отличает это, хотя это может быть понятно, назвав соответствующие переменные.

    • Он также позволяет понимать столбцы, по которым соединение/подмножество выполняется немедленно, просматривая эту строку кода (и не имея трассировки на соответствующую строку setkey()).

    • В операциях, где столбцы добавляются или обновляются по ссылке, операции on= гораздо более эффективны, так как не требуется, чтобы вся таблица данных была переупорядочена только для добавления/обновления столбцов. Например,

      ## compare 
      setkey(X, a, b) # why physically reorder X to just add/update a column?
      X[Y, col := i.val]
      
      ## to
      X[Y, col := i.val, on=c("a", "b")]
      

      Во втором случае нам не пришлось переупорядочивать. Он не вычисляет порядок, требующий много времени, но физически переупорядочивающий data.table в ОЗУ, и, избегая этого, мы сохраняем первоначальный порядок, и он также является перформативным.

    • Даже в противном случае, если вы повторяете повторное соединение, не должно быть заметной разницы в производительности между ключевыми и ad-hoc-соединениями.

Это приводит к вопросу о том, какое преимущество имеет ключевое слово data.table?

  • Есть ли преимущество в использовании data.table?

    Ключ data.table физически переупорядочивает его на основе этих столбцов (ов) в ОЗУ. Вычисление заказа обычно не занимает много времени, а скорее переупорядочение. Однако, как только мы отсортировали данные в ОЗУ, строки, принадлежащие к одной и той же группе, все смежны в ОЗУ и, следовательно, очень эффективны в кеше. Это сортировка, которая ускоряет операции с ключами data.tables.

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

В большинстве случаев поэтому больше не нужно устанавливать ключи. Мы рекомендуем использовать on= везде, где это возможно, если только ключ настройки не имеет существенного улучшения производительности, которое вы хотели бы использовать.

Вопрос:. Как вы думаете, будет ли производительность такой, как по сравнению с ключевым соединением, если вы используете setorder() для изменения порядка данных и использования on=? Если вы следовали до сих пор, вы должны выяснить это: -).

Ответ 2

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

Если вы не понимаете индексы, рассмотрите это: телефонная книга "индексируется" по имени. Поэтому, если я хочу найти номер телефона, это довольно просто. Но предположим, что я хочу искать по номеру телефона (например, искать, у кого есть конкретный номер телефона)? Если я не могу "пересчитать" телефонную книгу по номеру телефона, это займет очень много времени.

Рассмотрим следующий пример: предположим, что у меня есть таблица ZIP, всех почтовых индексов в США ( > 33 000) вместе со связанной информацией (город, штат, население, средний доход и т.д.). Если я хочу искать информацию для определенного почтового индекса, поиск (фильтр) примерно в 1000 раз быстрее, если сначала я setkey(ZIP,zipcode).

Еще одно преимущество связано с объединениями. Предположим, что в таблице данных есть список людей и их почтовые индексы (назовите это "PPL" ), и я хочу добавить информацию из таблицы ZIP (например, город, штат и т.д.). Следующий код сделает это:

setkey(ZIP,zipcode)
setkey(PPL,zipcode)
full.info <- PPL[ZIP, nomatch=F]

Это "соединение" в том смысле, что я соединяю информацию из двух таблиц, основанных на общем поле (zipcode). Подобные соединения на очень больших таблицах чрезвычайно медленны с кадрами данных и чрезвычайно быстрые с таблицами данных. В реальном примере мне пришлось сделать более 20 000 объединений, подобных этому, в полной таблице почтовых индексов. С таблицами данных script заняло около 20 минут. бежать. Я даже не пробовал его с кадрами данных, потому что это заняло бы более 2 недель.

ИМХО, вы не должны просто читать, но изучать часто задаваемые вопросы и вводный материал. Это легче понять, если у вас есть настоящая проблема, чтобы применить это.

[Ответ на комментарий @Frank]

Re: сортировка и индексирование. На основе ответа на этот вопрос кажется, что setkey(...) действительно переставляет столбцы в таблице (например, физическая сортировка) и не создает индекс в смысле базы данных. Это имеет некоторые практические последствия: с одной стороны, если вы установите ключ в таблице с помощью setkey(...), а затем измените любое из значений в столбце ключа, data.table просто объявит, что таблица больше не сортируется (отключив sorted); он динамически повторно индексирует не, чтобы поддерживать правильный порядок сортировки (как это было бы в базе данных). Кроме того, "удаление ключа" с помощью setky(DT,NULL) выполняет не восстановление таблицы в исходном, несортированном порядке.

Re: filter vs. join - практическая разница заключается в том, что фильтрация извлекает подмножество из одного набора данных, тогда как объединение объединяет данные из двух наборов данных на основе общего поля. Существует много разных видов соединения (внутренний, внешний, левый). Приведенный выше пример является внутренним соединением (возвращаются только записи с ключами, общие для обеих таблиц), и это имеет много общего с фильтрацией.