Этот документ рассказывает об использовании data.table
в качестве
зависимости в других R-пакетах. Если вас интересует использование C-кода
data.table
в приложениях, не связанных с R, или прямой вызов его
C-функций, перейдите к последнему разделу этого руководства.
Импортирование data.table
не отличается от импортирования других пакетов
R. Этот раздел предназначен для ответа на самые распространенные вопросы по
этой теме; представленные здесь уроки могут быть применены и к другим
пакетам R.
data.table
Одной из главных особенностей data.table
является его лаконичный
синтаксис, который делает исследовательский анализ быстрее и легче как для
написания, так и для восприятия. Эта удобство может побудить авторов пакетов
использовать data.table
. Ещё более важной причиной является высокая
производительность. Передавая ресурсоёмкие вычислительные задачи из вашего
пакета в data.table
, вы получаете максимальную производительность, не
прибегая к созданию собственных методов числовой оптимизации.
data.table
простоИспользовать data.table
в качестве зависимости очень просто, так как у
data.table
нет собственных зависимостей. Это касается как операционной
системы, так и зависимостей R. Если у вас установлена R, то на вашем
устройстве уже есть всё необходимое для установки data.table
. Это также
означает, что добавление data.table
в качестве зависимости для вашего
пакета не вызовет цепочку других рекурсивных зависимостей, что делает
установку удобной даже в режиме офлайн.
DESCRIPTION
{#DESCRIPTION}Первое место для определения зависимостей в пакете - это файл DESCRIPTION
. Чаще всего вам нужно добавить data.table
в поле Imports:
. Это потребует установки data.table
перед компиляцией/установкой вашего пакета. Как упоминалось выше, никакие другие пакеты устанавливаться не будут, поскольку data.table
не имеет собственных зависимостей. Вы также можете указать минимально необходимую версию зависимости; например, если ваш пакет использует функцию fwrite
, которая появилась в data.table
в версии 1.9.8, вы должны включить это как Imports: data.table (>= 1.9.8)
. Таким образом, вы сможете убедиться, что установленная версия data.table
- 1.9.8 или более поздняя, прежде чем ваши пользователи смогут установить ваш пакет. Помимо поля Imports:
, вы также можете использовать Depends: data.table
, но мы настоятельно не рекомендуем использовать этот подход (и, возможно, запретим его в будущем), поскольку он загружает data.table
в рабочее пространство вашего пользователя; то есть, он включает функциональность data.table
в скрипты ваших пользователей без их запроса. Imports:
- это правильный способ использовать data.table
в вашем пакете, не навязывая data.table
его пользователю. Мы надеемся, что поле Depends:
в R со временем будет упразднено, поскольку это справедливо для всех пакетов.
NAMESPACE
{#NAMESPACE}Следующий шаг - это определить, какие функции из data.table
использует ваш
пакет. Это нужно сделать в файле NAMESPACE
. Наиболее часто авторы пакетов
используют import(data.table)
, что позволит импортировать все
экспортируемые (то есть перечисленные в собственном файле NAMESPACE
пакета
data.table
) функции из data.table
.
Возможно, вы захотите использовать только часть функций data.table
;
например, некоторые пакеты могут использовать только высокопроизводительные
функции чтения и записи CSV из data.table
. В этом случае вы можете
добавить importFrom(data.table, fread, fwrite)
в ваш файл
NAMESPACE
. Также возможно импортировать все функции из пакета, за
исключением определенных, используя import(data.table, except=c(fread,
fwrite))
.
Не забудьте также ознакомиться с примечанием о нестандартных вычислениях в
data.table
в разделе «Неопределенные глобальные переменные»
В качестве примера мы зададим две функции в пакете a.pkg
, который
использует data.table
. Одна функция, gen
, будет генерировать простой
data.table
; другая, aggr
, будет выполнять простую агрегацию этой
data.table
.
gen = function (n = 100L) { dt = as.data.table(list(id = seq_len(n))) dt[, grp := ((id - 1) %% 26) + 1 ][, grp := letters[grp] ][] } aggr = function (x) { stopifnot( is.data.table(x), "grp" %in% names(x) ) x[, .N, by = grp] }
Обязательно включите тесты в ваш пакет. Перед каждым крупным релизом
data.table
мы проверяем опубликованные пакеты, которые зависят от нас. Это
означает, что если изменения в data.table
нарушат ваш код, мы сможем
обнаружить эти проблемы и уведомить вас до выпуска новой версии. Это
предполагает, что вы опубликуете ваш пакет в CRAN или Bioconductor. Самый
базовый тест может быть представлен в виде простого R-скрипта в каталоге
вашего пакета tests/test.R
:
library(a.pkg) dt = gen() stopifnot(nrow(dt) == 100) dt2 = aggr(dt) stopifnot(nrow(dt2) < 100)
При тестировании вашего пакета вы можете использовать команду R CMD check
--no-stop-on-test-error
, которая продолжит выполнение после ошибки и
запустит все ваши тесты (в отличие от остановки на первой строке скрипта с
ошибкой). Имейте в виду, что это требует R версии 3.4.0 или выше.
testthat
Очень часто для тестирования используется пакет testthat
. Тестирование
пакета, который импортирует data.table
, не отличается от тестирования
других пакетов. Пример тестового скрипта tests/testthat/test-pkg.R
:
context("pkg tests") test_that("generate dt", { expect_true(nrow(gen()) == 100) }) test_that("aggregate dt", { expect_true(nrow(aggr(gen())) < 100) })
Если data.table
находится в разделе Suggests (но не в Imports), то вам
нужно написать .datatable.aware=TRUE
в одном из файлов R/*, чтобы избежать
ошибок "объект не найден" при тестировании с помощью
testthat::test_package
или testthat::test_check
.
Пока что R CMD check
может выдавать предупреждения при виде нестандартных
вычислений R, используемых при работе с data.table
(особенно с левой
стороны :=
) . Во время проверки пакета это приводит к сообщениям NOTE
следующего типа:
* checking R code for possible problems ... NOTE aggr: no visible binding for global variable 'grp' gen: no visible binding for global variable 'grp' gen: no visible binding for global variable 'id' Undefined global functions or variables: grp id
Самый простой способ справиться с этим — предварительно определить эти
переменные в вашем пакете и установить их как NULL
, при необходимости
добавив комментарий (как это сделано в уточнённой версии функции gen
ниже). При возможности также можно использовать вектор символов вместо
символов (как в функции aggr
ниже):
gen = function (n = 100L) { id = grp = NULL # избегаем предупреждений от R CMD check dt = as.data.table(list(id = seq_len(n))) dt[, grp := ((id - 1) %% 26) + 1 ][, grp := letters[grp] ][] } aggr = function (x) { stopifnot( is.data.table(x), "grp" %in% names(x) ) x[, .N, by = "grp"] }
Случай со специальными символами data.table
(например, .SD
и .N
) и
оператором присвоения (:=
) немного отличается (см. ?.N
для получения
дополнительной информации, включая полный список таких символов). Вам
следует импортировать те значения, которые вы используете, из пространства
имён data.table
, чтобы защититься от возможных проблем, если в будущем мы
изменим экспортируемое значение этих символов, например, если вы хотите
использовать .N
, .I
и :=
, минимальный NAMESPACE
будет содержать:
importFrom(data.table, .N, .I, ':=')
Гораздо проще использовать import(data.table)
, что позволит коду вашего
пакета использовать любой объект, экспортируемый из data.table
.
Если вам не мешает, что id
и grp
будут зарегистрированы как глобальные
переменные в пространстве имен вашего пакета, вы можете использовать
?globalVariables
. Учтите, что эти примечания не влияют на код или его
функциональность; если вы не собираетесь публиковать свой пакет, вы можете
их проигнорировать.
options()
требуется осторожностьПакеты R могут использовать систему опций, которые пользователь задаёт при
помощи options(имя=значение)
. Чтобы узнать значение опции, используют
getOption("имя", значение_по_умолчанию)
. Аргументы функций по-умолчанию
часто включат вызов getOption()
, чтобы пользователь знал (из ?fun
или
args(fun)
), какое имя параметра управляет значением по умолчанию для этого
параметра; например, fun(..., verbose=getOption("datatable.verbose",
FALSE))
. Все опции data.table
начинаются с datatable.
, чтобы не
конфликтовать с параметрами других пакетов. Пользователь просто вызывает
options(datatable.verbose=TRUE)
, чтобы включить этот режим. Это влияет на
все вызовы функций data.table
, если явно не указан verbose=FALSE
;
например, fun(..., verbose=FALSE)
.
Механизм опций в R является глобальным. Это означает, что если
пользователь устанавливает опцию data.table
для собственного пользования,
то эта установка также влияет на код в любом пакете, использующем
data.table
. Для такой опции, как datatable.verbose
, это желаемое
поведение, поскольку целью является отслеживание и протоколирование всех
операций data.table
, откуда бы они ни исходили; включение этой опции не
влияет на результаты. Еще одна уникальная для R и полезная на практике опция
— это options(warn=2)
, которая превращает все предупреждения в ошибки. Её
цель состоит в том, чтобы не пропустить ни одного предупреждения при
практическом использовании кода. Есть 6 опций datable.print.*
и 3 опции
оптимизации, которые не влияют на результат операций. Однако есть одна опция
data.table
, которая на него влияет и в данный момент вызывает опасения:
datatable.nomatch
. Эта опция изменяет выполняемое по-умолчанию соединение
с внешнего на внутреннее. [Внешнее соединение используется по умолчанию,
потому что оно не отбрасывает отсутствующие данные и поэтому считается
безопаснее; кроме того, оно соответствует тому, как имена и индексы работают
в самом R.] Некоторые пользователи предпочитают, чтобы по умолчанию
использовалось внутреннее соединение, и мы предусмотрели для них эту
опцию. Однако пользователь, установивший эту опцию, может непреднамеренно
изменить поведение соединений внутри пакетов, использующих
data.table
. Соответственно, в версии 1.12.4 (октябрь 2019) при
использовании опции datatable.nomatch
выводилось сообщение, а начиная с
версии 1.14.2 она теперь игнорируется с предупреждением. Это была
единственная опция data.table
, вызывавшая подобные опасения.
Если вы столкнулись с проблемами при создании пакета, использующего
data.table
, убедитесь, что проблема воспроизводится в новой сессии R,
используя консоль R: R CMD check package.name
.
Некоторые из наиболее распространенных проблем, с которыми сталкиваются
разработчики, обычно связаны с вспомогательными инструментами,
предназначенными для автоматизации некоторых задач разработки пакета,
например, использование roxygen
для генерации файла NAMESPACE
из
метаданных в файлах кода R. Другие связаны с вспомогательными функциями,
которые собирают и проверяют пакет. К сожалению, они иногда непредумышленно
приводят к побочным эффектам, которые могут скрыть источник ваших
проблем. Поэтому перепроверьте наличие импорта в файлах DESCRIPTION
и
NAMESPACE
в соответствии с инструкциями
выше.
Если Вы не можете воспроизвести возникшие проблемы при сборке и проверке при
помощи R в командной строке, Вы можете попробовать обратиться за поддержкой,
основываясь на прошлых проблемах, с которыми мы сталкивались при
взаимодействии data.table
с вспомогательными инструментами:
devtools#192 или
devtools#1472.
Начиная с версии 1.10.5, data.table
распространяется на условиях Mozilla
Public License (MPL). С причинами перехода с GPL можно ознакомиться
здесь, а подробнее о
MPL Вы можете прочитать в Википедии
здесь и
здесь.
data.table
: SuggestsЕсли Вы хотите использовать data.table
необязательным образом, то есть
только тогда, когда он установлен, вам следует использовать Suggests:
data.table
в вашем файле DESCRIPTION
вместо Imports: data.table
. По
умолчанию, data.table
не будет установлена при установке Вашего
пакета. Это также потребует от Вас добавления проверок на наличие
data.table
в код Вашего пакета, что можно сделать с помощью функции
?requireNamespace
. Приведенный ниже пример демонстрирует условное
использование быстрой записи CSV из data.table
при помощи функции
?fwrite
. Если пакет data.table
не установлен, вместо него используется
гораздо более медленная базовая функция R ?write.table
.
my.write = function (x) { if(requireNamespace("data.table", quietly=TRUE)) { data.table::fwrite(x, "data.csv") } else { write.table(x, "data.csv") } }
Несколько более расширенная версия этого способа также гарантирует, что
установленная версия data.table
является достаточно свежей, чтобы в ней
была доступна функция fwrite
:
my.write = function (x) { if(requireNamespace("data.table", quietly=TRUE) && utils::packageVersion("data.table") >= "1.9.8") { data.table::fwrite(x, "data.csv") } else { write.table(x, "data.csv") } }
При использовании пакета в качестве предполагаемой зависимости не следует
использовать import
в файле NAMESPACE
. Достаточно упомянуть его в файле
DESCRIPTION
. При использовании функций data.table
в коде пакета (файлы
R/*
) необходимо использовать префикс data.table::
, поскольку ни одна из
них не импортируется. При использовании data.table
в тестах пакета
(например, в файлах tests/testthat/test*
) необходимо создать переменную
.datatable.aware=TRUE
в одном из файлов R/*
.
data.table
в Imports
, но функции явно не импортированыНекоторые пользователи
(пример) могут
предпочесть отказаться от использования importFrom
или import
в файле
NAMESPACE
и вместо этого использовать полное имя data.table::*
в своём
коде (разумеется, сохраняя data.table
в секции Imports:
файла
DESCRIPTION
).
В этом случае неэкспортированная функция [.data.table
превратится в вызов
[.data.frame
в качестве меры предосторожности, поскольку data.table
не
имеет возможности узнать, что родительский пакет осведомлен о том, что он
пытается выполнить вызов синтаксиса API запросов data.table
(что может
привести к неожиданному поведению, поскольку структура вызовов
[.data.frame
и [.data.table
принципиально отличается: например,
последний имеет гораздо больше аргументов).
Если Вы предпочитаете такой подход к разработке пакетов, задайте переменную
.datatable.aware = TRUE
в любом месте исходного кода R (экспортировать её
не нужно). Это сообщит data.table
, что Вы, как разработчик пакета,
спроектировали свой код так, чтобы намеренно полагаться на функциональность
data.table
, даже если это может быть не очевидно при просмотре вашего
файла NAMESPACE
.
data.table
на лету определяет, знает ли вызывающая функция, что она
обращается к data.table
, с помощью внутренней функции cedta
(«Calling Environment is Data Table Aware», окружение
вызова функции знает про data.table
), которая, помимо проверки
?getNamespaceImports
для вашего пакета, также проверяет существование этой
переменной (и некоторые другие вещи).
Более официальную документацию о зависимостях пакетов и способах их объявления можно найти в официальном руководстве: Writing R Extensions.
Некоторые из внутренне используемых подпрограмм на C теперь экспортированы
для другого кода на C и могут быть использованы в пакетах R непосредственно
из кода на C. Подробнее о том, как это делать, см. в
?cdt
и в
разделе Writing R
Extensions
Linking to native routines in other packages.
Некоторые небольшие части Си-кода data.table
были изолированы от R C API и
теперь могут быть использованы из приложений, не относящихся к R, путем
компоновки с файлами .so / .dll. Более подробная информация об этом будет
предоставлена позже, а пока вы можете изучить Си-код, который был изолирован
от R C API в
src/fread.c
и
src/fwrite.c.
Чтобы преобразовать зависимость Вашего пакета от data.table
типа Depends
в зависимость типа Imports
, выполните следующие действия:
До:
Depends: R (>= 3.5.0), data.table Imports:
После:
Depends: R (>= 3.5.0) Imports: data.table
R CMD check
Запустите R CMD check
, чтобы выявить недостающие импорты. Этот шаг:
data.table
,
которые не были импортированы явно..N
, .SD
и :=
.Замечание: R CMD check
ловит не все подобные случаи использования. В
частности, R CMD check
пропускает некоторые символы/функции в формулах и
полностью пропустит собранные из текста выражения типа parse(text =
"data.table(a = 1)")
. Для обнаружения таких краевых случаев пакетам
потребуется хорошее покрытие тестами.
Основываясь на результатах R CMD check
, импортируйте из data.table
все
необходимые функции, специальные символы, общие функции S3 и классы S4.
Это означает добавление директив importFrom(data.table, ...)
для символов,
функций и S3-генериков, а при необходимости также директив
importClassesFrom(data.table, ...)
для S4-классов. Подробности о том, как
правильно это сделать, см. в руководстве "Writing R Extensions".
В качестве альтернативы вы можете импортировать всё, что экспортирует
data.table
, хотя обычно это не рекомендуется:
import(data.table)
Причины, по которым полный импорт лучше не делать:
1. Документация: Файл NAMESPACE может служить хорошей документацией о том, как Ваших зависимостей от других пакетов.
2. Избежание конфликтов: Полный импорт может привести к ошибкам, которые сложно диагностировать и исправлять. Например, если вы используете import(pkgA)
и import(pkgB)
, но позже pkgB
экспортирует функцию, которую уже экспортирует pkgA
, это приведет к поломке вашего пакета из-за конфликтов в вашем пространстве имен, что запрещено R CMD check
и CRAN.
Когда вы перемещаете пакет из Depends
в Imports
, он больше не будет
автоматически подключаться при подключении вашего пакета. Это может быть
важно для примеров, тестов, руководств и демо-файлов, где пакеты, указанные
в Imports
, должны быть подключены явным образом.
До (с Depends
):
# функции data.table доступны напрямую library(MyPkgDependsDataTable) dt <- data.table(x = 1:10, y = letters[1:10]) setDT(dt) result <- merge(dt, other_dt, by = "x")
После (с использованием Imports
):
# Явным образом загрузить data.table в пользовательских скриптах или руководствах пакетов library(data.table) library(MyPkgDependsDataTable) dt <- data.table(x = 1:10, y = letters[1:10]) setDT(dt) result <- merge(dt, other_dt, by = "x")
Imports
Depends
изменяет путь search()
ваших
пользователей, возможно, без их желания.search()
, что делает процесс загрузки чище и
потенциально быстрее.Depends
может со временем привести к конфликтам и проблемам совместимости.Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.