library(data.table)
<- fread("data/kiva/kiva_loans.csv") loans_dt
tidyverse vs. data.table
Как вы уже, наверное, убедились, базовый R умеет очень много, в том числе и для работы с данными. Однако какие-то операции все равно выполнить довольно непросто.
Возьмем, например, задачу агрегации: вам нужно посчитать средний размер кредитов отдельно для каждого вида активности. Групп много, поэтому считать для каждой группы отдельно – это неудобно, долго и попросту некрасиво. В базовом R для этого есть специальная функция aggregate()
, но она довольно неудобная.
Поэтому стали появляться пакеты, которые пытаются сделать агрегацию и другие непростые операции максимально безболезненными способами. Основных таких пакетов два: {data.table}
и {tidyverse}
. Это огромные пакеты, которые очень сильно изменяют работу в R, в том числе в плане стиля и используемой парадигмы. Тем не менее, в основе своей стоит все то, что мы прошли раньше.
Подход {data.table}
{data.table}
– это распространенный пакет, который позволяет анализировать датафреймы максимально быстро и с помощью очень лаконичного кода.
Во-первых, {data.table}
очень быстрый. Он добивается большей скорости за счет того, что очень грамотно написан, минимизируя затрачиваемое время на выполнение операций.
Давайте импортируем наш набор данных. Для этого воспользуемся функцией fread() из пакета {data.table}
.
“f” в fread()
означает “fast and friendly”: эта функция очень быстрая и довольно хорошо угадывает формат текстовой таблицы.
Давайте запустим бенчмарк на наших данных для сравнения чтения данных с помощью {data.table}
, read.csv()
из базового R, а так же двух подходов из tidyverse.
library(microbenchmark)
<- microbenchmark(
bench_results read.csv("data/kiva/kiva_loans.csv"),
::fread("data/kiva/kiva_loans.csv"),
data.table::read_csv("data/kiva/kiva_loans.csv", show_col_types = FALSE),
readr::vroom("data/kiva/kiva_loans.csv", show_col_types = FALSE)
vroom )
Warning in microbenchmark(read.csv("data/kiva/kiva_loans.csv"),
data.table::fread("data/kiva/kiva_loans.csv"), : less accurate nanosecond times
to avoid potential integer overflows
Как видим, {data.table}
быстрее примерно в 5 раз, чем базовая функция read.csv()
и в 2 раза быстрее, чем функция из read_csv()
из пакета {readr}
. Только пакет {vroom}
оказался быстрее (про него мы поговорим позже).
На сайте пакета {data.table} есть ссылка на бенчмарк, в котором сравниваются не только пакеты R, но и другие инструменты анализа данных, например, питоновский pandas – самый используемый пакет для анализа данных в Python. Конечно, {data.table}
обгоняет его на порядок!
loans_dt
id funded_amount loan_amount activity sector
1: 653051 300 300 Fruits & Vegetables Food
2: 653053 575 575 Rickshaw Transportation
3: 653068 150 150 Transportation Transportation
4: 653063 200 200 Embroidery Arts
5: 653084 400 400 Milk Sales Food
---
671201: 1340323 0 25 Livestock Agriculture
671202: 1340316 25 25 Livestock Agriculture
671203: 1340334 0 25 Games Entertainment
671204: 1340338 0 25 Livestock Agriculture
671205: 1340339 0 25 Livestock Agriculture
use
1: To buy seasonal, fresh fruits to sell.
2: to repair and maintain the auto rickshaw used in their business.
3: To repair their old cycle-van and buy another one to rent out as a source of income
4: to purchase an embroidery machine and a variety of new embroidery materials.
5: to purchase one buffalo.
---
671201: [True, u'para compara: cemento, arenya y ladriollo para construir una pila.'] - this loan use has been approved by VIVA QA
671202: [True, u'to start a turducken farm.'] - this loan use has been approved by VIVA QA
671203:
671204: [True, u'to start a turducken farm.'] - this loan use has been approved by VIVA QA
671205: [True, u'to start a turducken farm.'] - this loan use has been approved by VIVA QA
country_code country region currency partner_id
1: PK Pakistan Lahore PKR 247
2: PK Pakistan Lahore PKR 247
3: IN India Maynaguri INR 334
4: PK Pakistan Lahore PKR 247
5: PK Pakistan Abdul Hakeem PKR 245
---
671201: PY Paraguay Concepción USD 58
671202: KE Kenya KES 138
671203: KE Kenya KES 138
671204: KE Kenya KES 138
671205: KE Kenya KES 138
posted_time disbursed_time funded_time
1: 2014-01-01 06:12:39 2013-12-17 08:00:00 2014-01-02 10:06:32
2: 2014-01-01 06:51:08 2013-12-17 08:00:00 2014-01-02 09:17:23
3: 2014-01-01 09:58:07 2013-12-17 08:00:00 2014-01-01 16:01:36
4: 2014-01-01 08:03:11 2013-12-24 08:00:00 2014-01-01 13:00:00
5: 2014-01-01 11:53:19 2013-12-17 08:00:00 2014-01-01 19:18:51
---
671201: 2017-07-25 16:55:34 2017-07-25 07:00:00 <NA>
671202: 2017-07-25 06:14:08 2017-07-24 07:00:00 2017-07-26 02:09:43
671203: 2017-07-26 00:02:07 2017-07-25 07:00:00 <NA>
671204: 2017-07-26 06:12:55 2017-07-25 07:00:00 <NA>
671205: 2017-07-26 06:31:46 2017-07-25 07:00:00 <NA>
term_in_months lender_count tags
1: 12 12
2: 11 14
3: 43 6 user_favorite, user_favorite
4: 11 8
5: 14 16
---
671201: 13 0
671202: 13 1
671203: 13 0
671204: 13 0
671205: 13 0
borrower_genders repayment_interval date
1: female irregular 2014-01-01
2: female, female irregular 2014-01-01
3: female bullet 2014-01-01
4: female irregular 2014-01-01
5: female monthly 2014-01-01
---
671201: female monthly 2017-07-25
671202: female monthly 2017-07-25
671203: monthly 2017-07-26
671204: female monthly 2017-07-26
671205: female monthly 2017-07-26
class(loans_dt)
[1] "data.table" "data.frame"
Дататейбл – это “улучшенный” датафрейм: с ним работают все те функции, которые мы применяли для датафрейма, специальные функции для дататейбла, а что-то работает немного по-другому по сравнению с датафреймом. Например, оператор [
, т.е. квадратные скобки.
Давайте посмотрим по-внимательнее как это происходит на примере расчета среднего размера кредитов, группируя по активности:
mean_loan = mean(loan_amount)), by = activity] loans_dt[, .(
activity mean_loan
1: Fruits & Vegetables 828.7252
2: Rickshaw 531.2891
3: Transportation 864.0621
4: Embroidery 676.1450
5: Milk Sales 739.8786
---
159: Event Planning 1435.0000
160: Celebrations 280.0000
161: Computer 985.1852
162: Personal Care Products 514.2857
163: Mobile Transactions 875.1157
Сразу уже усложним задачу: возьмем только агрикультурный сектор и отранжируем по среднему кредиту:
= sector] loans_dt[, .N, by
sector N
1: Food 136657
2: Transportation 15518
3: Arts 12060
4: Services 45140
5: Agriculture 180302
6: Manufacturing 6208
7: Wholesale 634
8: Retail 124494
9: Clothing 32742
10: Construction 6268
11: Health 9223
12: Education 31013
13: Personal Use 36385
14: Housing 33731
15: Entertainment 830
== "Agriculture", .(mean_loan = mean(loan_amount)), by = activity][order(-mean_loan)] loans_dt[sector
activity mean_loan
1: Beekeeping 1472.5000
2: Animal Sales 1137.0304
3: Livestock 1127.6670
4: Agriculture 1015.4628
5: Cattle 1012.5048
6: Flowers 852.8319
7: Poultry 845.8295
8: Land Rental 809.8375
9: Farm Supplies 787.7585
10: Veterinary Sales 787.7525
11: Aquaculture 762.2685
12: Farming 708.4624
13: Dairy 659.3063
14: Pigs 471.4168
Уух! Выглядит монструозно, да? Зато как мы все сделали используя минимальное количество знаков. Заметьте, что здесь необычного для нас:
Не нужно прописывать
loans_dt$sector
, поиск переменной будет начинаться с колонок дататейбла.Там, где мы раньше выбирали колонки, мы еще и расчеты можем вести.
Внутри квадратных скобок появилась вторая запятая, т.е. третье поле, в котором мы прописали группировку.
Несколько операций прописываются путем соединения квадратных скобочек, код превращается в эдакий паровозик.
Разработчики {data.table}
делают особый акцент на “консервативности” пакета: у него нет никаких зависимостей, ему достаточно очень старой версии R, функционирование пакета не будет ломаться из-за выкинутых устаревших функций. В общем, {data.table}
очень суров и уважаем программистами. Он и не особо пытается понравиться рядовым пользователям. Зато освоив его, вы сможете творить магию: то, что с помощью базового R, tidyverse или Python будет выполняться очень долго (если выполнится вообще), {data.table}
сможет сделать гораздо быстрее, иногда в десятки и сотни раз!
Очень сильно, не правда ли? Чем же может ответить tidyverse?
Подход tidyverse
Не пугайтесь сообщений, все в порядке. Во-первых, пакет {tidyverse}
– это не просто пакет, а “пакет с пакетами” (да-да, как у вас дома), который подключает сразу несколько других пакетов, которые составляют ядро tidyverse. Список и версии этих пакетов {tidyverse}
выводит при подключении.
library(tidyverse)
── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
✔ ggplot2 3.4.0 ✔ purrr 1.0.1
✔ tibble 3.2.1 ✔ dplyr 1.1.0
✔ tidyr 1.3.0 ✔ stringr 1.5.0
✔ readr 2.1.3 ✔ forcats 1.0.0
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::between() masks data.table::between()
✖ dplyr::filter() masks stats::filter()
✖ dplyr::first() masks data.table::first()
✖ dplyr::lag() masks stats::lag()
✖ dplyr::last() masks data.table::last()
✖ purrr::transpose() masks data.table::transpose()
<- read_csv("data/kiva/kiva_loans.csv") loan_tbl
Rows: 671205 Columns: 20
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (10): activity, sector, use, country_code, country, region, currency, t...
dbl (6): id, funded_amount, loan_amount, partner_id, term_in_months, lende...
dttm (3): posted_time, disbursed_time, funded_time
date (1): date
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
За пределами ядра tidyverse есть специальный пакет для скоростной загрузки данных – {vroom}
.
Функция read_csv()
(не путать с функцией из базового R – read.csv()
!) возвращает тиббл – “улучшенный” датафрейм, примерно как это было с дататейблом.
Теперь же сделаем то же самое с нашими данными, что мы делали с помощью d
:
%>%
loan_tbl filter(sector == "Agriculture") %>%
group_by(activity) %>%
summarise(mean_loan = mean(loan_amount)) %>%
arrange(desc(mean_loan))
# A tibble: 14 × 2
activity mean_loan
<chr> <dbl>
1 Beekeeping 1472.
2 Animal Sales 1137.
3 Livestock 1128.
4 Agriculture 1015.
5 Cattle 1013.
6 Flowers 853.
7 Poultry 846.
8 Land Rental 810.
9 Farm Supplies 788.
10 Veterinary Sales 788.
11 Aquaculture 762.
12 Farming 708.
13 Dairy 659.
14 Pigs 471.
Очень сильно отличается от того, как мы работали раньше! Хотя в основе лежит все тот же R. Код, написанный в tidyverse, нарочито многословен (особенно по сравнению с {data.table}
), каждая отдельная операция имеет свою функцию. Писать нужно больше, зато это гораздо легче: меньше нужно думать, какими хитрыми трюками сделать преобразование данных. Нужно просто разделить весь процесс преобразования данных на отдельные операции и последовательно прописать их. Код получается аккуратный и очень читаемый, даже для человека, который не знает tidyverse или даже R в целом. Даже этот новый оператор %>%
выглядит довольно понятно: его можно прочитать как “затем”.
Заметьте, что tidyverse выводит очень подробные сообщения, которые даже выглядят очень красиво: со всякими иконками, красивым форматированием. Разработчики tidyverse работают над тем, чтобы делать свой интерфейс максимально понятным для пользователя: говорящие сами за себя названия функций, куча удобных фишек на все случаи жизни.
tidyverse постоянно обновляется, регулярно появляются новые функции, а старые функции заменяются на более удобные новые. И это не всегда плюс: обновив пакеты, установленные год назад, вы можете обнаружить, что старый код перестал работать! Мол, мы тут придумали, как сделать лучше, переписывайте код заново (или используйте старые версии пакетов).
Разработчики tidyverse, в целом, не стремится за высокой скоростью. Часто можно заметить, что новые функции работают довольно медленно. Но если у вас строчек меньше миллиона, то разницу в скорости с {data.table}
вы едва ли заметите.
Команда разработчиков tidyverse работает на компанию Posit (бывшая RStudio). Поэтому в RStudio вы найдете несколько “шпаргалок” для tidyverse, но не для {data.table}
. Они также активно активно работают над популяризацией tidyverse, стараясь сделать вход в него максимально комфортным, особенно для людей без опыта программирования. tidyverse команда открыто заявляет о своей политике diversity, некоторые члены этой команды – открытые представители гендерных и сексуальных меньшинств.
{data.table} vs tidyverse
Так что же лучше: {data.table}
или tidyverse? Это один из самых частых споров в R-комьюнити. У обоих подходов есть плюсы, которые можно обсуждать вечно. Сегодня tidyverse выигрывает в популярности, особенно за пределами русскоязычного пространства.
В последнее время {data.table}
и tidyverse все меньше противостоят друг другу и все больше взаимодополняют. Например, некоторые используют в качестве основного инструмента tidyverse, но при работе с данными побольше переключаются на {data.table}
. Кроме того, сами разработчики tidyverse пытаются приладить суперскоростной {data.table}
в tidyverse: пакет {dtplyr}
позволяет “переводить” код, написанный в tidyverse в код на {data.table}
.