Feb 6: data.table pt.1
Таблицы
Таблица — это набор наблюдений по строкам и пространство признаков этих наблюдений в виде набора колонок. Базовый типа для таблиц в R — data.frame
. На уровне структуры data.frame
это все те же списки, в которых могут храниться разные по типу объекты, однако с требованием равенства длины объектов. Важно: все значения одной колонки могут быть только одного типа (потому что это по сути векторы), а не как в Excel, OpenOffice или каком другом табличном процессоре.
data.frame vs data.table vs dplyr
Несмотря на то, что базовый тип таблиц это data.frame
, в настоящее время используются варианты надстроек над этим типом: формат data.table
или формат tibble
из пактов data.table
и dplyr
соответственно. Различить их можно по элементам синтаксиса. В частности, data.frame
почти всегда использует оператор $
(my_table$my_var
), в data.table
активно используется оператор :=
, а в tibble
- оператор %>%
.
Пример создания таблицы и выбора строки по условию в data.frame
:
# создаём таблицу
set.seed(1234)
df <- data.frame(
var1 = sample(letters, 5),
var2 = sample(1:5, 5)
)
# смотрим результат
print(df)
## var1 var2
## 1 p 1
## 2 v 5
## 3 e 2
## 4 l 3
## 5 o 4
# обращаемся к значениям первой колонки
df$var1
## [1] "p" "v" "e" "l" "o"
# выводим все строки, где в колонке var2 значения меньше или равны 3
df[df$var2 <= 3, ]
## var1 var2
## 1 p 1
## 3 e 2
## 4 l 3
Аналогичные операции в tibble
-формате:
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
## # A tibble: 3 × 2
## var1 var2
## <chr> <int>
## 1 p 1
## 2 e 2
## 3 l 3
data.table
Преимущества data.table
высокая скорость IO / манипуляций (бенчмарки)
параллелизация вычислений по умолчанию
опирается только на base R
лаконичность выражений
бережные апдейты (поддерживается R 3.1)
забота об обратной совместимости
Установка и подключение пакета
# устанавливаем пакет (однократно)
install.packages('data.table')
# подключаем пакет (при каждой сессии)
library(data.table)
Создание data.table-таблиц
Создать data.table
можно следующим образом (синтаксис немного напоминает создание именованного списка, как и для всех форматов таблиц):
# подключаем пакет, если не был подключен ранее
library(data.table)
##
## Attaching package: 'data.table'
## The following objects are masked from 'package:dplyr':
##
## between, first, last
# создаем датасет
dt1 <- data.table(
month_names = month.name,
month_abb = month.abb,
month_ord = seq_len(length(month.abb)),
is_winter = grepl('Jan|Dec|Feb', month.abb)
)
print(dt1)
## month_names month_abb month_ord is_winter
## 1: January Jan 1 TRUE
## 2: February Feb 2 TRUE
## 3: March Mar 3 FALSE
## 4: April Apr 4 FALSE
## 5: May May 5 FALSE
## 6: June Jun 6 FALSE
## 7: July Jul 7 FALSE
## 8: August Aug 8 FALSE
## 9: September Sep 9 FALSE
## 10: October Oct 10 FALSE
## 11: November Nov 11 FALSE
## 12: December Dec 12 TRUE
Основная формула dt-синтаксиса
Общая формула data.table
выглядит как dataset[выбор строк, операции над колонками, группировка]
. То есть, указание, какие строки необходимо выделить, осуществляется в первой части (до первой запятой в синтаксисе data.table
). Если нет необходимости выделять какие-то строки, перед первой запятой ничего не ставится. Параметр группировки (как и прочие параметры, кроме i
и j
опциональны).
Также можно провести параллели с синтаксисом SQL-запроса. В терминах SQL data.table-выражения выглядят как таблица[where, select, group by]
.
Выбор строки
Выбор строк в data.table
осуществляется аналогично выбору элементов в векторе — по номеру строки или по какому-то условию. При выборе по номеру строки также можно указать вектор номеров строк, которые необходимо вернуть. При выборке строки по условию проверяется, удовлетворяет ли условию каждый элемент строки в определённой колонке, и если удовлетворяет, выделяется вся строка.
# выбор по одному номеру строки
dt1[1, ]
## month_names month_abb month_ord is_winter
## 1: January Jan 1 TRUE
# выбор по нескольким номерам строк
# сначала создаём вектор номеров строк
my_rows <- c(2, 5, 8)
# выводим строки, которые мы указали в векторе
dt1[my_rows]
## month_names month_abb month_ord is_winter
## 1: February Feb 2 TRUE
## 2: May May 5 FALSE
## 3: August Aug 8 FALSE
# или, аналогично, сразу указываем, какие строки хотим выделить
dt1[c(2, 5, 8)]
## month_names month_abb month_ord is_winter
## 1: February Feb 2 TRUE
## 2: May May 5 FALSE
## 3: August Aug 8 FALSE
Выбор по условию: мы сразу указываем название колонки, к значениям которых будем применять условие-фильтр. Писать в стиле dt1[dt1$month_ord <= 3]
избыточно, data.table понимает просто название колонки.
# выводим все строки, в которых в колонке month_ord значения меньше или равны 3
dt1[month_ord <= 3]
## month_names month_abb month_ord is_winter
## 1: January Jan 1 TRUE
## 2: February Feb 2 TRUE
## 3: March Mar 3 FALSE
Работа с колонками
Обращение к колонке
В синтаксисе data.table
все операции над колонками производятся после первой запятой. Выделение колонок также относится к операциям над колонками. Для выделения одной или нескольких колонок необходимо просто указать лист (список) с названиями колонки или колонок.
Если указать название колонки, то будут возвращены значения из этой колонки. Если название обернуть в list()
, то будет возвращена таблица, с которой будет одна эта колонка:
dt1[, month_names]
## [1] "January" "February" "March" "April" "May" "June"
## [7] "July" "August" "September" "October" "November" "December"
dt1[, list(month_names)]
## month_names
## 1: January
## 2: February
## 3: March
## 4: April
## 5: May
## 6: June
## 7: July
## 8: August
## 9: September
## 10: October
## 11: November
## 12: December
Если посмотреть структуру, то оба объекта будут различаться — вектор строковых значений и таблица соответственно:
str(dt1[, month_names])
## chr [1:12] "January" "February" "March" "April" "May" "June" "July" ...
## Classes 'data.table' and 'data.frame': 12 obs. of 1 variable:
## $ month_names: chr "January" "February" "March" "April" ...
## - attr(*, ".internal.selfref")=<externalptr>
Выбор нескольких колонок
Если мы хотим на основе большой таблицы создать новую таблицу, с какими-то определенными колонками, мы их можем также перечислить в list()
:
dt1[, list(month_names, month_abb)]
## month_names month_abb
## 1: January Jan
## 2: February Feb
## 3: March Mar
## 4: April Apr
## 5: May May
## 6: June Jun
## 7: July Jul
## 8: August Aug
## 9: September Sep
## 10: October Oct
## 11: November Nov
## 12: December Dec
При таком выделении можно сразу переименовывать колонки. Строго говоря, создаётся таблица с новой колонкой с требуемым именем, в которую записываются значения колонки, которую надо переименовать. Например:
# выделяем в отдельную таблицу колонку month_names, month_abb
# колонку month_names переименовываем в new_m_names
dt3 <- dt1[, list(new_m_names = month_names, month_abb)]
print(dt3)
## new_m_names month_abb
## 1: January Jan
## 2: February Feb
## 3: March Mar
## 4: April Apr
## 5: May May
## 6: June Jun
## 7: July Jul
## 8: August Aug
## 9: September Sep
## 10: October Oct
## 11: November Nov
## 12: December Dec
Cоздание колонок
Создать новую колонку в синтаксисе data.table можно с помощью оператора :=
. Это точно такая же операция над колонками, как и все прочие, просто происходит создание новой колонки:
dt1[, new_col := 12:1]
dt1
## month_names month_abb month_ord is_winter new_col
## 1: January Jan 1 TRUE 12
## 2: February Feb 2 TRUE 11
## 3: March Mar 3 FALSE 10
## 4: April Apr 4 FALSE 9
## 5: May May 5 FALSE 8
## 6: June Jun 6 FALSE 7
## 7: July Jul 7 FALSE 6
## 8: August Aug 8 FALSE 5
## 9: September Sep 9 FALSE 4
## 10: October Oct 10 FALSE 3
## 11: November Nov 11 FALSE 2
## 12: December Dec 12 TRUE 1
Модификация колонок
Оператор :=
позволяет изменять объект на месте, поэтому мы можем просто колонке присвоить новое значение. Фактически мы на основе старой колонки создаем вектор новых значений и записываем его в в колонку с тем же названием.
dt1[, new_col := new_col + 5]
dt1
## month_names month_abb month_ord is_winter new_col
## 1: January Jan 1 TRUE 17
## 2: February Feb 2 TRUE 16
## 3: March Mar 3 FALSE 15
## 4: April Apr 4 FALSE 14
## 5: May May 5 FALSE 13
## 6: June Jun 6 FALSE 12
## 7: July Jul 7 FALSE 11
## 8: August Aug 8 FALSE 10
## 9: September Sep 9 FALSE 9
## 10: October Oct 10 FALSE 8
## 11: November Nov 11 FALSE 7
## 12: December Dec 12 TRUE 6
Можно совмещать фильтрацию по строкам и модификацию колонок. Например, для всех строк, где в колонке month_ord
значения меньше или равны 5, в колонке new_col
проставляем NA
:
dt1[month_ord <= 5, new_col := NA]
dt1
## month_names month_abb month_ord is_winter new_col
## 1: January Jan 1 TRUE NA
## 2: February Feb 2 TRUE NA
## 3: March Mar 3 FALSE NA
## 4: April Apr 4 FALSE NA
## 5: May May 5 FALSE NA
## 6: June Jun 6 FALSE 12
## 7: July Jul 7 FALSE 11
## 8: August Aug 8 FALSE 10
## 9: September Sep 9 FALSE 9
## 10: October Oct 10 FALSE 8
## 11: November Nov 11 FALSE 7
## 12: December Dec 12 TRUE 6
Удаление колонок
Удаление колонок осуществляется схожим образом, просто колонке присваивается значение NULL
## удаление колонок
dt1[, new_col := NULL]
dt1
## month_names month_abb month_ord is_winter
## 1: January Jan 1 TRUE
## 2: February Feb 2 TRUE
## 3: March Mar 3 FALSE
## 4: April Apr 4 FALSE
## 5: May May 5 FALSE
## 6: June Jun 6 FALSE
## 7: July Jul 7 FALSE
## 8: August Aug 8 FALSE
## 9: September Sep 9 FALSE
## 10: October Oct 10 FALSE
## 11: November Nov 11 FALSE
## 12: December Dec 12 TRUE
Манипуляции с таблицами
rbind()
Функция rbind()
(от row bind
) используется для объединение двух или более таблиц по строкам. То есть, в результате получается таблица с таким же количеством колонок, но с увеличенным числом строк - по количеству строк в объединяемых таблицах.
Нередко в объединяемых таблицах отсутствует какая-нибудь колонка или колонки перепутаны. В таких случаях необходимо использовать аргументы use.names = TRUE
(проверка названий колонок при объединение) и fill = TRUE
(создание колонки с NA
-значениями). Обратите внимание, это работает только с data.table
-объектами.
# создаем первую таблицу
df1 <- data.table(tb = 'table_1',
col1 = sample(9, 3),
col3 = 'only in table1',
col2 = sample(letters, 3))
# создаем вторую таблицу
df2 <- data.table(tb = 'table_2',
col4 = 'only in table2',
col1 = sample(9, 3),
col2 = sample(letters, 3))
# объединяем по строкам
rbind(df1, df2, fill = TRUE)
## tb col1 col3 col2 col4
## 1: table_1 2 only in table1 z <NA>
## 2: table_1 7 only in table1 f <NA>
## 3: table_1 6 only in table1 o <NA>
## 4: table_2 4 <NA> x only in table2
## 5: table_2 6 <NA> d only in table2
## 6: table_2 8 <NA> y only in table2
Полезные ссылки
Моя серия вебинаров по data.table. Есть как запись, так и конспект. На занятиях мы будем рассматривать лишь половину или треть материала вебинаров.
Базовые операции одновременно в data.table и dplyr-синтаксисе. Много полезных приемов, и, в целом, наглядно. Смотрите блоки по data.table, dplyr синтаксис можно игнорировать или смотреть для общего развития (это весьма часто используемый синтаксис в академии).
Перевод документации data.table от Андрея Огурцова. Полезно для понимания разных нюансов работы data.table
Продвинутый data.table для желающих, много неочевидных нюансов и трюков.
Экзотические возможности и ключевые слова, для совсем экстремалов. Заметка важна в первую очередь внутренними ссылками на разные релевантные и поясняющие ресурсы.
Домашнее задание
импорт данных
- подключите библиотеку data.table (установите, если не была установлена)
- скачайте файл titanic3.csv
- с помощью команды
titanic <- fread('titanic3.csv')
импортируйте файл в рабочее окружение. Прочитайте справку по функции fread и попробуйте импортировать данные без сохранения на диск. - посмотрите с помощью команды
class()
объекта. если он отличается отdata.table
- сконвертируйте в data.table
работа со строками
- выберите случайным образом 5 строк из таблицы
titanic
- выберите те строки, где возраст пассажира меньше 1 (младенцы)
- выберите те строки, где возраст пассажира в диапазоне 20 до 50 лет
- выберите строки по выжившим младенцам (survived)
- выберите пассажирок, которые имеют титул
lady
(используйтеgrep()
/grepl()
, прочитайте справку по этим функциям и примеры использования)
работа с колонками
- посчитайте средний возраст пассажиров
- аналогично, посчитайте
summary()
по возрасту женщин - выделите в отдельный датасет всех погибших пассажиров, оставьте для них только значения пола, возраста и класса билета (
pclass
), переменнуюpclass
переименуйте вclass
- в полученном датасете посчитайте количество пассажиров, их средний и медианный возраст, разброс по возрасту (
sd()
)