require(data.table) knitr::opts_chunk$set( comment = "#", error = FALSE, tidy = FALSE, cache = FALSE, collapse = TRUE) .old.th = setDTthreads(1)
В этом руководстве рассматривается стандартное использование функций
изменения формы данных melt
(из широкой в длинную) и dcast
(из длинной в
широкую) для класса data.table, а также новые расширенные возможности
«расплавления» и «отлива» для нескольких столбцов, доступные с v1.9.6
.
options(width = 100L)
Мы будем загружать наборы данных непосредственно по ходу текста.
Функции melt
и dcast
для data.table
предназначены для изменения формы
таблиц между «длинными» и «широкими»; реализации специально разработаны с
учетом больших данных в памяти (например, 10 ГБ).
В этом описании мы
Сначала кратко рассмотрим стандартные операции melt
и dcast
для
data.table
, чтобы преобразовать их из широкого формата в длинный и
наоборот
Рассмотрим сценарии, в которых текущие функции становятся громоздкими и неэффективными
Наконец, обратим внимание на новые улучшения методов melt
и dcast
,
позволяющие обрабатывать несколько столбцов таблиц data.table
одновременно.
Расширенная функциональность соответствует философии data.table
, которая
заключается в эффективном и понятном выполнении операций.
Предположим, у нас есть data.table
(заполненная искусственными данными),
как показано ниже:
s1 <- "family_id age_mother dob_child1 dob_child2 dob_child3 1 30 1998-11-26 2000-01-29 NA 2 27 1996-06-22 NA NA 3 26 2002-07-11 2004-04-05 2007-09-02 4 32 2004-10-10 2009-08-27 2012-07-21 5 29 2000-12-05 2005-02-28 NA" DT <- fread(s1) DT ## dob значит "дата рождения" (date of birth). str(DT)
DT
в длинную форму, где каждое значение dob
представляет собой отдельное наблюдение.Мы могли бы добиться этого с помощью melt()
, указав аргументы id.vars
и
measure.vars
следующим образом:
DT.m1 = melt(DT, id.vars = c("family_id", "age_mother"), measure.vars = c("dob_child1", "dob_child2", "dob_child3")) DT.m1 str(DT.m1)
measure.vars
указывает набор столбцов, которые мы хотели бы свернуть
(или объединить) вместе.
Мы также можем указать номера столбцов вместо их имен.
По умолчанию столбец variable
является фактором. Установите для
аргумента variable.factor
значение FALSE
, если вместо этого вы хотите
вернуть вектор типа character
.
По умолчанию «расплавленные» столбцы автоматически получают имена
variable
и value
.
melt
сохраняет атрибуты столбцов в результатах.
variable
и value
соответственно child
и dob
DT.m1 = melt(DT, measure.vars = c("dob_child1", "dob_child2", "dob_child3"), variable.name = "child", value.name = "dob") DT.m1
По умолчанию, если один из аргументов id.vars
или measure.vars
отсутствует, остальные столбцы автоматически достаются отсутствующему
аргументу.
Если не указаны ни id.vars
, ни measure.vars
, как указано в ?melt
,
все не-числовые, целые, логические столбцы достанутся id.vars
.
Кроме того, выдается предупреждающее сообщение, в котором выделяются столбцы, которые автоматически считаются id.vars
.
dcast
(«отливка») data.table
(из длинной в широкую)В предыдущем разделе мы рассмотрели, как перейти от широкой формы к длинной. В этом разделе мы рассмотрим обратную операцию.
DT
из DT.m1
?Мы бы хотели собрать все наблюдения child, соответствующие каждой паре
family_id, age_mother
, вместе в одной строке. Мы можем сделать это с
помощью dcast
следующим образом:
dcast(DT.m1, family_id + age_mother ~ child, value.var = "dob")
dcast
использует формульный интерфейс. Переменные в левой части
формулы соответствуют id (строкам), а в правой части — measure
(столбцам).
value.var
обозначает столбец, который будет заполнен при приведении к
широкому формату.
dcast
также старается сохранять атрибуты в результатах, где это
возможно.
DT.m1
, как нам узнать количество детей в каждой семье?Вы также можете передать функцию для агрегирования в dcast
с аргументом
fun.aggregate
. Это особенно важно, когда переданная формула даёт больше
одного наблюдения для каждой описываемой ею ячейки.
dcast(DT.m1, family_id ~ ., fun.agg = function(x) sum(!is.na(x)), value.var = "dob")
Ознакомьтесь с ?dcast
, чтобы узнать о других полезных аргументах и
примерах.
melt
/dcast
До сих пор мы видели функции melt
и dcast
, которые эффективно
реализованы для data.table
, используя внутренние механизмы data.table
(быстрая поразрядная сортировка, двоичный поиск и т.д.).
Однако бывают ситуации, когда требуемая операция не может быть выражена
простым способом. Например, рассмотрим data.table
, показанную ниже:
s2 <- "family_id age_mother dob_child1 dob_child2 dob_child3 gender_child1 gender_child2 gender_child3 1 30 1998-11-26 2000-01-29 NA 1 2 NA 2 27 1996-06-22 NA NA 2 NA NA 3 26 2002-07-11 2004-04-05 2007-09-02 2 2 1 4 32 2004-10-10 2009-08-27 2012-07-21 1 1 1 5 29 2000-12-05 2005-02-28 NA 2 1 NA" DT <- fread(s2) DT ## 1 = female, 2 = male
Предположим, Вы хотите объединить (melt
) все столбцы dob
вместе, а также
столбцы gender
вместе. Используя описанные выше функции, можно сделать
примерно следующее:
DT.m1 = melt(DT, id = c("family_id", "age_mother")) DT.m1[, c("variable", "child") := tstrsplit(variable, "_", fixed = TRUE)] DT.c1 = dcast(DT.m1, family_id + age_mother + child ~ variable, value.var = "value") DT.c1 str(DT.c1) ## столбец gender теперь класса IDate!
Мы хотели объединить все столбцы типа dob
и gender
соответственно. Вместо этого мы объединяем их все, а затем снова
разделяем. Думаю, легко заметить, что это довольно запутанный путь (и
неэффективный).
В качестве аналогии представьте, что у вас есть шкаф с четырьмя полками для одежды, и вы хотите сложить одежду с полок 1 и 2 вместе (в 1), а также 3 и 4 вместе (в 3). Мы делаем примерно следующее: объединяем всю одежду в одну кучу, а затем рассовываем ее обратно по полкам 1 и 3!
Столбцы, передаваемые в melt
, могут быть разных типов. Если «сплавлять»
их вместе, результат будет приведён к единому типу.
Нам приходится создавать дополнительный столбец, разбивая столбец
variable
на два других, назначение которых совершенно неочевидно. Мы
делаем это потому, что он нужен нам для вызова dcast
на следующем шаге.
Наконец, мы «отливаем» набор данных. Проблема в том, что эта операция требует гораздо больше вычислений, чем melt. В частности, она требует вычисления порядка переменных в формуле, а это дорого.
Между прочим, stats::reshape
способна выполнить эту операцию очень простым
способом. Это чрезвычайно полезная и часто недооцененная функция. Вам
определенно стоит её попробовать!
melt
Поскольку мы хотели бы, чтобы data.table
выполняла эту операцию просто и
эффективно, используя тот же интерфейс, мы пошли дальше и реализовали
дополнительную функциональность, которая позволяет «выплавлять» несколько
столбцов одновременно.
melt
для нескольких столбцов одновременноИдея довольно проста. Мы передаем список столбцов в measure.vars
, где
каждый элемент списка содержит столбцы, которые должны быть объединены
вместе.
colA = paste0("dob_child", 1:3) colB = paste0("gender_child", 1:3) DT.m2 = melt(DT, measure = list(colA, colB), value.name = c("dob", "gender")) DT.m2 str(DT.m2) ## тип столбца сохранён
При необходимости мы можем удалить столбец variable
.
Эта функция реализована полностью на языке C, поэтому является быстрой и экономичной по памяти, а также простой.
patterns()
Обычно в таких задачах столбцы, которые мы хотели бы объединить, можно
выделить по шаблону. Для этого мы можем использовать функцию
patterns()
. Приведенную выше операцию можно переписать так:
DT.m2 = melt(DT, measure = patterns("^dob", "^gender"), value.name = c("dob", "gender")) DT.m2
measure()
для задания measure.vars
через шаблон или разделительЕсли, как в приведенных выше данных, входные столбцы для расплавления имеют
обычные имена, то можно использовать measure
, что позволяет указать
столбцы для «переплавки» через разделитель или регулярное
выражение. Например, рассмотрим данные по ирисам,
(two.iris = data.table(datasets::iris)[c(1,150)])
Про ирисы известно четыре числовых столбца, имена которых имеют регулярную
структуру: сначала цветочная часть, затем точка, затем длина или
ширина. Чтобы указать, что мы хотим «расплавить» эти четыре столбца, мы
можем использовать measure
с sep="."
, что означает использование
strsplit
для всех имен столбцов; те столбцы, в которых после разбиения
получается максимальное количество групп, будут использованы в качестве
measure.vars
:
melt(two.iris, measure.vars = measure(part, dim, sep="."))
Первые два аргумента measure
в приведенном выше коде (part
и dim
)
используются для названия выходных столбцов; количество аргументов должно
быть равно максимальному количеству групп после разбиения с помощью sep
.
Если мы хотим получить два столбца значений, по одному для каждой части, мы
можем использовать специальное ключевое слово value.name
, которое означает
вывод столбца значений для каждого уникального имени, найденного в данной
группе:
melt(two.iris, measure.vars = measure(value.name, dim, sep="."))
Используя приведенный выше код, мы получаем один столбец значений для каждой части цветка. Если мы вместо этого хотим получить столбец значений для каждого измерения, мы можем сделать следующее:
melt(two.iris, measure.vars = measure(part, value.name, sep="."))
Возвращаясь к примеру с данными о семьях и детях, можно предложить более
сложное использование measure
, включающее функцию, которая используется
для преобразования строковых значений child
в целые числа:
DT.m3 = melt(DT, measure = measure(value.name, child=as.integer, sep="_child")) DT.m3
В приведенном выше коде мы использовали sep="_child"
, что привело к
«расплавлению» только столбцов, содержащих эту строку (шесть имен столбцов,
разбитых на две группы). Аргумент child=as.integer
означает, что вторая
группа приведет к созданию выходного столбца с именем child
и значениями,
определенными путем подстановки строк из этой группы в функцию as.integer
.
Наконец, мы рассмотрим пример (заимствованный из пакета tidyr
), в котором
нам необходимо определить группы с помощью регулярного выражения, а не
разделителя.
(who <- data.table(id=1, new_sp_m5564=2, newrel_f65=3)) melt(who, measure.vars = measure( diagnosis, gender, ages, pattern="new_?(.*)_(.)(.*)"))
Аргумент pattern
должен быть Perl-совместимым регулярным выражением,
захватывающим столько же групп (подвыражений, заключенных в скобки), сколько
и других аргументов (имен групп). В приведенном ниже коде показано, как
использовать более сложное регулярное выражение с пятью группами, два
числовых столбца вывода и анонимную функцию преобразования типов,
melt(who, measure.vars = measure( diagnosis, gender, ages, ymin=as.numeric, ymax=function(y) ifelse(nzchar(y), as.numeric(y), Inf), pattern="new_?(.*)_(.)(([0-9]{2})([0-9]{0,2}))" ))
dcast
Отлично! Теперь мы можем «расплавлять» данные в нескольких столбцах
одновременно. Теперь, если взять набор данных DT.m2
, полученный выше, как
мы можем вернуться к тому же формату, что и исходные данные, с которых мы
начали?
Если мы используем обычный dcast
, то нам придется дважды выполнять «отлив»
и связывать результаты вместе. Но это опять же многословно, не совсем просто
и к тому же неэффективно.
value.var
одновременноТеперь можно передавать несколько столбцов value.var
для dcast
,
чтобы операции выполнялись внутри data.table
и эффективным образом.
## новая функция 'dcast' - несколько value.vars DT.c2 = dcast(DT.m2, family_id + age_mother ~ variable, value.var = c("dob", "gender")) DT.c2
Атрибуты по возможности сохраняются в результате.
Всё выполнено внутренними средствами, быстро и экономично по памяти.
fun.aggregate
:Вы также можете передать несколько функций в fun.aggregate
для
dcast
. Ознакомьтесь с примерами в ?dcast
, которые иллюстрируют эту
функциональность.
setDTthreads(.old.th)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.