Яка мета встановити ключ у data.table?


113

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


Одне я читав джерело ?setkey.

setkey()сортує а data.tableта позначає як відсортовану. Впорядковані стовпці є ключовими. Ключем можуть бути будь-які стовпці в будь-якому порядку. Стовпці завжди сортуються у порядку зростання. Таблиця змінюється за посиланням. Жодна копія не робиться, окрім тимчасової робочої пам'яті, розміром якої є один стовпець.

Моє вирішення тут полягає в тому, що ключ "сортувати" таблицю даних. Це призведе до дуже схожого ефекту order(). Однак це не пояснює мету мати ключ.


Поширені питання 3.2. та 3.3 пояснюють:

3.2 У мене немає ключа на великому столі, але групування все ще дуже швидко. Чому так?

data.table використовує радіаційне сортування. Це значно швидше, ніж інші алгоритми сортування. Див ?base::sort.list(x,method="radix"). Radix спеціально для цілих чисел, див . Це також одна з причин, чому setkey()це швидко. Якщо ключ не встановлений, або ми групуємо в іншому порядку, ніж ключ, ми називаємо це ad hoc by.

3.3 Чому групування за стовпцями у ключі швидше, ніж спеціальне?

Оскільки кожна група є суміжною в оперативній пам’яті, тим самим мінімізуючи кількість сторінок, і пам'ять може бути скопійована масово ( memcpyв C), а не циклічно в C.

Звідси я здогадуюсь, що встановлення клавіші якось дозволяє R використовувати "радіаційне сортування" за іншими алгоритмами, і тому це швидше.


У 10-хвилинному швидкому путівнику також є інструкція по клавішах.

  1. Ключі

Почнемо з розгляду data.frame, спеціально назви рядків (або англійською мовою, назви рядків). Тобто кілька імен, що належать до одного ряду. Кілька імен, що належать до одного ряду? Це не те, до чого ми звикли в data.frame. Ми знаємо, що кожен рядок має щонайменше одне ім’я. Людина має щонайменше два імені, перше ім’я та друге ім’я. Це корисно, наприклад, організувати телефонний довідник, який сортується за прізвищем, а потім за першою назвою. Однак кожен рядок у data.frame може мати лише одне ім’я.

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

Унікальність не застосовується, тобто допускаються повторювані значення ключів. Оскільки рядки відсортовані за ключем, будь-які дублікати в ключі з’являться послідовно

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


"Я думаю, що встановлення ключа якось дозволяє R використовувати" радіаційне сортування "над іншими алгоритмами" - я взагалі не отримую цього від допомоги. Я читаю, що встановлення ключа сортується за клавішею. Ви можете проводити "ad hoc" сортування за іншими стовпцями, ніж ключові, і це швидко, але не так швидко, як якщо б ви вже відсортували.
Арі Б. Фрідман

Я думаю, що двійковий пошук швидше, ніж векторне сканування під час вибору рядків. Я не комп'ютер, тому не знаю, що це насправді означає. Крім FAQ, дивіться вступ .
Френк

Відповіді:


125

Незначне оновлення: також зверніться до нових віньєток HTML . У цьому питанні висвітлюються інші віньєтки, які ми плануємо.


Я знову оновив цю відповідь (лютий 2016 р.) З огляду на нову on=функцію, яка дозволяє також приєднатися до спеціальних дій . Див. Історію для попередніх (застарілих) відповідей.

Що саме робить setkey(DT, a, b)?

Це робить дві речі:

  1. впорядковує рядки таблиці данихDT за стовпцями ( а , б ) за посиланням , завжди в порядку збільшення .
  2. знаки цих стовпців в якості ключових стовпців, встановивши атрибут , званий sortedв DT.

Переупорядкування є швидким (завдяки внутрішній сортування радіоактивних таблиць data.table ) і ефективною пам'яттю ( виділяється лише один додатковий стовпець типу подвійний ).

Коли це setkey()потрібно?

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

## "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=аргументу?

    Причин досить багато.

    1. Це дозволяє чітко розрізнити операцію як операцію, що включає дві таблиці даних . Це X[Y]також не відрізняє цього, хоча це може бути зрозумілим, називаючи змінні відповідним чином.

    2. Це також дозволяє зрозуміти стовпці, в яких з'єднання / підмножина виконується негайно, переглянувши цей рядок коду (і не потрібно відстежувати відповідний setkey()рядок).

    3. У операціях, де стовпці додаються або оновлюються за посиланням , 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")]

      У другому випадку нам не довелося проводити впорядкування. Це не обчислення часу, яке займає багато часу, а фізичне переупорядкування даних datatable у оперативній пам'яті, і, уникаючи його, ми зберігаємо оригінальне замовлення, і воно також є виконавчим.

    4. Навіть в іншому випадку, якщо ви не виконувати приєднання повторно, не повинно бути помітної різниці в продуктивності між ключами та тимчасовими приєднаннями.

Це призводить до питання, яку перевагу вже має клавіша даних.table ?

  • Чи є перевага для керування таблицею даних?

    Якщо зберегти таблицю даних, вона фізично переробляє її на основі цих стовпців (ів) в ОЗУ. Обчислення замовлення зазвичай не займає багато часу, скоріше переупорядкування . Однак, як тільки ми відсортували дані в оперативній пам’яті, рядки, що належать до однієї групи, всі суміжні в оперативній пам’яті, і тому дуже ефективні в кеші. Саме впорядкованість прискорює операції над ключовими таблицями data.tables.

    Тому важливо з’ясувати, чи варто витрачати час на переупорядкування всього таблиця даних. Час, щоб зробити об'єднання / агрегацію, ефективну кешем. Зазвичай, якщо немає операцій групування / об'єднання, які повторюються, виконуються на одному ключі таблиць data.table, різниці не повинно бути помітною.

Тому в більшості випадків більше не потрібно встановлювати клавіші. Ми рекомендуємо використовувати, on=де це можливо, якщо тільки клавіша налаштування не суттєво покращить продуктивність, яку ви хочете використовувати.

Запитання: Як ви думаєте, якою буде продуктивність порівняно з ключовим з'єднанням, якщо ви використовуєте setorder()для впорядкування таблиці даних та її використання on=? Якщо ви до цього часу дотримувались, ви зможете це зрозуміти :-).


3
Класно, дякую! До сих пір я не замислювався над тим, що насправді означає "бінарний пошук", і насправді не розумів причину, чому він використовується замість хеша.
Френк

@Arun, це DT[J(1e4:1e5)]дійсно рівнозначно DF[DF$x > 1e4 & DF$x < 1e5, ]? Не могли б ви вказати мені, що Jозначає? Також пошук не повертає жодних рядків, оскільки sample(1e4, 1e7, TRUE)не включає числа вище 1e4.
рибалка

@fishtank, в цьому випадку це має бути >=і <=- зафіксовано. J.) є псевдонімами list(тобто вони еквівалентні). Внутрішньо, коли iце список, він перетворюється на таблицю даних, після якої двійковий пошук використовується для обчислення індексів рядків. Виправлено, 1e4щоб 1e5уникнути плутанини. Дякуємо, що помітили. Зауважте, що тепер ми можемо безпосередньо використовувати on=аргумент для виконання двійкових підмножин, а не для встановлення ключа. Детальніше читайте з нових HTML-віньет . І слідкуйте за цією сторінкою за віньєтками для приєднання.
Арун

можливо, це може піти на більш ретельне оновлення? розділ "коли потрібно" видається застарілим, наприклад
MichaelChirico

Яка функція повідомляє про ключ, що використовується?
скан

20

Ключовим є в основному індекс у наборі даних, який дозволяє робити дуже швидкі та ефективні операції сортування, фільтрації та об’єднання. Це, мабуть, найкращі причини використовувати таблиці даних замість фреймів даних (синтаксис використання таблиць даних також набагато зручніший для користувачів, але це не має нічого спільного з ключами).

Якщо ви не розумієте індекси, врахуйте це: телефонна книга "індексується" по імені. Тож якщо я хочу шукати чийсь номер телефону, це досить просто. Але припустимо, я хочу шукати номер телефону (наприклад, шукати, хто має певний номер телефону)? Якщо я не можу "переіндексувати" телефонну книгу за номером телефону, це займе дуже багато часу.

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

Ще одна користь стосується приєднань. Припустимо, у таблиці даних є список людей та їх поштові індекси (називайте його "PPL"), і я хочу додати інформацію з таблиці ZIP (наприклад, місто, штат тощо). Наступний код зробить це:

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

Це "приєднання" в тому сенсі, що я приєднуюсь до інформації з 2 таблиць на базі загального поля (поштовий індекс). Подібне приєднання до дуже великих таблиць відбувається надзвичайно повільно з фреймами даних та надзвичайно швидко з таблицями даних. На прикладі реального життя мені довелося зробити більше 20 000 приєднань, як це, на повній таблиці поштових індексів. З таблицями даних сценарій зайняв близько 20 хв. бігти. Я навіть не пробував це з фреймами даних, оскільки це зайняло б більше двох тижнів.

ІМХО, ви не повинні просто читати, а вивчати матеріали поширених питань та вступ. Простіше зрозуміти, якщо у вас є реальна проблема, щоб застосувати це.

[Відповідь на коментар @ Франка]

Re: сортування проти індексації - Виходячи з відповіді на це питання , виявляється, що setkey(...)насправді переставляє стовпці таблиці (наприклад, фізичне сортування) і не створює індекс у сенсі бази даних. Це має певні практичні наслідки: для одного, якщо встановити ключ у таблиці з, setkey(...)а потім змінити будь-яке значення у стовпці клавіш, data.table просто оголошує таблицю більше не відсортованою (відключивши sortedатрибут); він не динамічно повторно індексує, щоб підтримувати належний порядок сортування (як це було б у базі даних). Також "вилучення ключа" за допомогою setky(DT,NULL)цього не робить відновити таблицю в вихідному, відсортоване порядок.

Re: filter vs. join - практична відмінність полягає в тому, що фільтрування витягує підмножину з одного набору даних, тоді як приєднання поєднує дані з двох наборів даних на основі загального поля. Існує багато різних видів з'єднання (внутрішня, зовнішня, ліва). Наведений вище приклад - це внутрішнє з'єднання (повертаються лише записи з ключами, спільними для обох таблиць), і це має багато подібності з фільтруванням.


1
+1. Щодо вашого першого речення ... це вже відсортовано так? І чи не приєднується окремий випадок фільтра (чи операція, яка займає фільтрування як перший крок)? Здається, що «краща фільтрація» підсумовує всю користь.
Френк

1
Або краще сканування, я думаю.
Вологі ноги

1
@jlhoward Дякую Моя попередня переконання полягала в тому, що сортування не входить до переваг встановлення ключа (оскільки, якщо ви хочете сортувати, вам слід просто сортувати), а також, що setkeyнасправді перетворює рядки безповоротно. Якщо це лише для відображення цілей, то як я друкую перші десять рядків відповідно до "справжнього" замовлення (що я бачив би перед setkey)? Я впевнений, що setkey(DT,NULL)цього не робить ... (продовження)
Френк

... (продовження) Також я не переглянув код пакету, але щоб приєднатися X[Y,...], потрібно "фільтрувати" рядки X за допомогою ключа. Зрозуміло, що інші речі трапляються після цього (стовпці Y стають доступними, і є неявна безвідмовна), але я все ще не сприймаю це як концептуально відмінна вигода. Я здогадуюсь, що ваша відповідь міститься в тих операціях, які ви могли б хотіти зробити, хоча, коли це може бути корисним.
Френк

1
@Frank - Так setkey(DT,NULL)вилучає ключ, але не впливає на порядок сортування. Поставив питання про це тут . Подивимось.
jlhoward
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.