Я использую data.table, и есть много функций, которые требуют от меня установки ключа (например X[Y]
). Таким образом, я хочу понять, что делает ключ, чтобы правильно устанавливать ключи в моих таблицах данных.
Я прочитал один источник ?setkey
.
setkey()
сортируетdata.table
и отмечает его как отсортированный. Отсортированные столбцы - это ключ. Ключом могут быть любые столбцы в любом порядке. Столбцы всегда отсортированы по возрастанию. Таблица изменена по ссылке. Никаких копий не производится, кроме временной рабочей памяти размером в один столбец.
Мой вывод заключается в том, что ключ будет «сортировать» data.table, что дает эффект, очень похожий на order()
. Однако это не объясняет, зачем нужен ключ.
В FAQ 3.2 и 3.3 data.table объясняется:
3.2 У меня нет ключа на большом столе, но группировка по-прежнему очень быстрая. Это почему?
data.table использует сортировку по основанию. Это значительно быстрее, чем другие алгоритмы сортировки. Radix предназначен только для целых чисел, см
?base::sort.list(x,method="radix")
. Это также одна из причин, почемуsetkey()
это быстро. Когда ключ не задан или мы группируем в порядке, отличном от порядка ключа, мы называем это специальным путем.3.3 Почему группировка по столбцам в ключе происходит быстрее, чем по специальному?
Поскольку каждая группа является смежной в ОЗУ, тем самым сводя к минимуму выборку страниц, а память можно копировать массово (
memcpy
в C), а не зацикливаться в C.
Отсюда я предполагаю, что установка ключа каким-то образом позволяет R использовать "поразрядную сортировку" по сравнению с другими алгоритмами, и поэтому это быстрее.
В 10-минутном кратком руководстве также есть руководство по клавишам.
- Ключи
Начнем с рассмотрения data.frame, в частности имен строк (или, по-английски, имен строк). То есть несколько имен, принадлежащих одной строке. Несколько имен, принадлежащих одной строке? Это не то, к чему мы привыкли в data.frame. Мы знаем, что каждая строка имеет не более одного имени. У человека есть как минимум два имени: первое и второе. Это полезно, например, для организации телефонного справочника, который отсортирован по фамилии, затем по имени. Однако каждая строка в data.frame может иметь только одно имя.
Ключ состоит из одного или нескольких столбцов с именами строк, которые могут быть целыми числами, множителями, символами или каким-либо другим классом, а не просто символами. Кроме того, строки сортируются по ключу. Следовательно, таблица data.table может иметь не более одного ключа, поскольку ее нельзя отсортировать более чем одним способом.
Уникальность не гарантируется, т. Е. Разрешены повторяющиеся значения ключей. Поскольку строки сортируются по ключу, любые дубликаты в ключе будут отображаться последовательно.
Телефонный справочник помог понять, что такое ключ, но кажется, что ключ ничем не отличается от столбца факторов. Кроме того, он не объясняет, зачем нужен ключ (особенно для использования определенных функций) и как выбрать столбец для установки в качестве ключа. Кроме того, кажется, что в data.table со временем в качестве столбца установка любого другого столбца в качестве ключа, вероятно, также испортит столбец времени, что делает его еще более запутанным, поскольку я не знаю, разрешено ли мне установить любой другой столбец как ключ. Может кто-нибудь просветите меня, пожалуйста?
источник
Ответы:
Незначительное обновление: см. Также новые виньетки HTML . В этом выпуске освещаются другие эпизоды, которые мы планируем выпустить .
Я снова обновил этот ответ (февраль 2016 г.) в свете новой
on=
функции, которая также позволяет выполнять специальные соединения. Более ранние (устаревшие) ответы см. В истории.Что именно делает
setkey(DT, a, b)
?Он делает две вещи:
DT
по столбцам, предоставленным ( a , b ) по ссылке , всегда в порядке возрастания .sorted
вDT
.Переупорядочение происходит быстро (благодаря внутренней сортировке по основанию data.table ) и эффективно с точки зрения памяти (только один дополнительный столбец типа double выделяется ).
Когда
setkey()
требуется?Для операций группировки
setkey()
это никогда не было абсолютным требованием. То есть мы можем выполнить холодный обход или обходной путь .Однако до
v1.9.6
этогоx[i]
необходимоkey
было установить соединения формыx
. С новымon=
аргументом из v1.9.6 + это больше не верно, и поэтому установка ключей здесь также не является абсолютным требованием.Обратите внимание, что
on=
аргумент может быть явно указан даже дляkeyed
объединений.Так в чем же причина
on=
аргументации?Причин довольно много.
Это позволяет четко различать операцию как операцию с двумя таблицами data.tables . Простое выполнение
X[Y]
также не различает этого, хотя это можно понять, если присвоить переменным соответствующее имя.Это также позволяет сразу понять столбцы, в которых выполняется объединение / подмножество , путем просмотра этой строки кода (без необходимости отслеживания соответствующей
setkey()
строки).В операциях, в которых столбцы добавляются или обновляются по ссылке ,
on=
операции намного более производительны, поскольку не нужно переупорядочивать всю таблицу data.table только для добавления / обновления столбца (столбцов). Например,Во втором случае заказывать заново не пришлось. Это не вычисление порядка, что отнимает много времени, а физическое изменение порядка таблицы data.table в ОЗУ, и, избегая этого, мы сохраняем исходный порядок, и он также является эффективным.
Даже в противном случае, если вы не выполняете соединения многократно, не должно быть заметной разницы в производительности между ключевыми и произвольными соединениями.
Это приводит к вопросу, какое преимущество имеет ввод data.table ?
Есть ли преимущество в использовании таблицы data.table?
Ключ data.table физически меняет ее порядок на основе этих столбцов в ОЗУ. Вычисление заказа обычно не занимает много времени, это скорее повторный заказ. . Однако после того, как мы отсортировали данные в ОЗУ, все строки, принадлежащие к одной группе, будут непрерывными в ОЗУ и, следовательно, очень эффективны в кешировании. Именно отсортированность ускоряет операции с ключевыми данными data.tables.
Поэтому важно выяснить, стоит ли время, потраченное на переупорядочение всей таблицы data.table, потраченного на выполнение эффективного кеширования соединения / агрегирования. Обычно, если не выполняются повторяющиеся операции группировки / соединения с одной и той же таблицей data.table с ключом , заметной разницы быть не должно.
Вопрос: Как вы думаете, какой будет производительность по сравнению с ключевым соединением, если вы используете
setorder()
для изменения порядка data.table и используетеon=
? Если вы следили за этим до сих пор, вы сможете понять это :-).источник
DT[J(1e4:1e5)]
действительно эквивалентDF[DF$x > 1e4 & DF$x < 1e5, ]
? Не могли бы вы указать мне, чтоJ
означает? Также этот поиск не будет возвращать никаких строк, посколькуsample(1e4, 1e7, TRUE)
не включает числа выше 1e4.>=
и<=
- исправлено.J
(и.
) являются псевдонимамиlist
(т. е. эквивалентны). Внутренне, когдаi
это список, он преобразуется в таблицу data.table, после которой двоичный поиск используется для вычисления индексов строк. Исправлено,1e4
чтобы1e5
избежать путаницы. Спасибо за внимание. Обратите внимание, чтоon=
теперь мы можем напрямую использовать аргумент для выполнения двоичных подмножеств, а не для установки ключа. Подробнее читайте в новых виньетках HTML . И следите за этой страницей, чтобы найти виньетки для присоединения.Ключ - это, по сути, индекс в наборе данных, который позволяет очень быстро и эффективно выполнять операции сортировки, фильтрации и объединения. Вероятно, это лучшие причины использовать таблицы данных вместо фреймов данных (синтаксис для использования таблиц данных также намного удобнее для пользователя, но это не имеет ничего общего с ключами).
Если вы не разбираетесь в индексах, учтите следующее: телефонная книга «индексируется» по имени. Так что если я хочу найти чей-то номер телефона, это довольно просто. Но предположим, что я хочу выполнить поиск по номеру телефона (например, найти, у кого есть конкретный номер телефона)? Если я не смогу «переиндексировать» телефонную книгу по номеру телефона, это займет очень много времени.
Рассмотрим следующий пример: предположим, у меня есть таблица ZIP всех почтовых индексов в США (> 33 000) вместе со связанной информацией (город, штат, население, средний доход и т. Д.). Если я хочу найти информацию по определенному почтовому индексу, поиск (фильтр) будет примерно в 1000 раз быстрее, если я
setkey(ZIP,zipcode)
сначала.Еще одно преимущество связано с объединениями. Предположим, у вас есть список людей и их почтовые индексы в таблице данных (назовите ее «PPL»), и я хочу добавить информацию из почтовой таблицы (например, город, штат и т. Д.). Следующий код сделает это:
Это «соединение» в том смысле, что я объединяю информацию из двух таблиц в общем поле (почтовый индекс). Подобные соединения в очень больших таблицах выполняются очень медленно с фреймами данных и очень быстрыми с таблицами данных. В реальном примере мне пришлось сделать более 20 000 таких соединений на полной таблице почтовых индексов. С таблицами данных скрипт занял около 20 минут. бежать. Я даже не пробовал это с фреймами данных, потому что это заняло бы больше 2 недель.
ИМХО нужно не просто читать, а изучать FAQ и вводный материал. Это легче понять, если у вас есть реальная проблема, к которой можно применить это.
[Ответ на комментарий @Frank]
Re: сортировка против индексации. Основываясь на ответе на этот вопрос , выяснилось, что
setkey(...)
на самом деле столбцы в таблице меняются (например, физическая сортировка) и не создается индекс в смысле базы данных. Это имеет некоторые практические последствия: с одной стороны, если вы установите ключ в таблице с помощью,setkey(...)
а затем измените любое из значений в ключевом столбце, data.table просто объявляет, что таблица больше не сортируется (путем отключенияsorted
атрибута); он делает не динамически повторно индекса для поддержания надлежащего порядка сортировки (как произошло бы в базе данных). Кроме того , «вынув ключ» , используяsetky(DT,NULL)
это не восстановить таблицу в исходном, отсортированный порядок.Re: фильтр или соединение - практическая разница в том, что фильтрация извлекает подмножество из одного набора данных, тогда как соединение объединяет данные из двух наборов данных на основе общего поля. Есть много разных видов соединения (внутреннее, внешнее, левое). Приведенный выше пример представляет собой внутреннее соединение (возвращаются только записи с ключами, общими для обеих таблиц), и это имеет много общего с фильтрацией.
источник
setkey
действительно необратимо меняет порядок строк. Если это только для отображения, то как мне напечатать первые десять строк в соответствии с «истинным» порядком (который я видел до setkey)? Я почти уверен,setkey(DT,NULL)
что не делает этого ... (продолжение)X[Y,...]
, вам нужно «отфильтровать» строки X с помощью ключа. Конечно, после этого происходят и другие вещи (столбцы Y становятся доступными, и есть неявный by-without-by), но я все еще не вижу в этом концептуально отличного преимущества. Я предполагаю, что ваш ответ выражается в терминах операций, которые вы, возможно, захотите выполнить, где различие может быть полезно.setkey(DT,NULL)
удаляет ключ, но не влияет на порядок сортировки. Задал вопрос об этом здесь . Посмотрим.