Изменения

R:Оптимизация/Импорт данных

12 719 байтов добавлено, 14:45, 28 мая 2014
м
Загрузка 2814-import-data.wiki
<!-- R:Оптимизация/Импорт данных -->
 
 
 
{{CC-BY-4.0|author=автором Артём Клевцов}}
 
{{Pkg-req-notice}}
 
При обработке данных большого объёма имеет смысл импортировать только ту часть данных, которая непосредственно участвует в обработке. Это целесообразно как с точки зрения расхода оперативной памяти, так и скорости выполнения операций поиска, сортировки и фильтрации данных.
== Импорт Формат CSV ==
=== Функция <code>read.table()</code> ===
Наиболее распространённым форматом данных для импорта в R является формат CSV. Для импорта файлов CSV в R предусмотрена функция <code>read.table()</code> и функци-обёртка (wrapper) <code>read.csv()</code>. С точки зрения скорости работы, параметры функции <code>read.table()</code>, заданные по умолчанию, не являются оптимальными. Приведём несколько рекомендаций по использованию функции <code>read.table()</code>:
* указать тип переменных, содержащихся в таблице с помощью аргумента <code>colClasses</code>;
* отключить поиск комментариев с помощью аргумента <code>comment.char = ""</code>.
Создадим таблицу данных содержащую <math>10^{56}</math> строк и 6 столбцов:
{{r-code|code=<nowiki>> N set.seed(123) # начальная точка генератора случайных чисел для воспроизводимости результатовn.rows <- 10^5L 4L # задаём количество наблюдений> DF data.df <- data.frame(a = sample(1:10^3L, Nn.rows, replace = TRUE), b = sample(1:10^3L, Nn.rows, replace = TRUE), c = sample(LETTERS[1:10], Nn.rows, replace = TRUE), e = rnorm(Nn.rows), d = rnorm(Nn.rows, 100, 15), f = runif(Nn.rows, 0, 10^3L))
</nowiki>}}
Рассмотрим таблицу более подробно. Начало таблицы:
{{r-code|code=<nowiki> head(DFdata.df)#> a b c e d f#> 1 719 104 C 288 311 J -1.8785 3538 8788.22 40007 187.53#> 2 551 448 H 789 325 D -0.5608 1015794 82.14 20897 167.46#> 3 865 236 I 409 871 E -0.5967 1178610 108.80 62270 640.57#> 4 879 411 884 329 B - 0.6442 1209727 107.39 97977 962.94#> 5 941 126 I 66 229 E -0.5443 1116191 103.78 90112 663.35#> 6 867 579 I - 46 357 C 1.3212 1043854 92.93 17875 306.34</nowiki>}}
Структура данных:
{{r-code|code=<nowiki>> str(DFdata.df)#> 'data.frame': 1000000 10000 obs. of 6 variables:#> $ a: int 719 551 865 879 66 867 344 786 898 933 288 789 409 884 941 46 529 893 552 457 ...#> $ b: int 104 448 236 411 229 579 718 221 68 275 311 325 871 329 126 357 931 876 821 22 ...#> $ c: Factor w/ 10 levels "A","B","C","D",..: 3 8 9 2 10 4 5 2 9 3 3 6 10 7 6 5 ...#> $ e: num -1.878 354 -0.561 579 -0.597 -861 0.644 -973 0.544 619 ...#> $ d: num 87.2 10188.1 11783 108.8 120.4 1117 107.8 103.1 ...#> $ f: num 400 208 623 980 901 187 168 641 962 664 ...</nowiki>}}
Типы переменных:
{{r-code|code=<nowiki>> sapply(DFdata.df, class)#> a b c e d f #> "integer" "integer" "factor" "numeric" "numeric" "numeric"</nowiki>}}
Размер объекта в оперативной памяти:
{{r-code|code=<nowiki>> print(object.size(DFdata.df), units = "auto")34#> 353.3 Mb7 Kb</nowiki>}}
Сохраним таблицу в csv-файл следующими командами:
{{r-code|code=<nowiki>> tmp.csv <- tempfile(fileext=".csv") # генерируем имя и путь для временного файла> write.tablecsv(DFdata.df, tmp.csv, sep = ";", row.names = FALSE, quote = FALSE)</nowiki>}}
Размер полученного файла составил:
{{r-code|code=<nowiki>> file.info(tmp.csv)$size # размер файла в байтах#> [1] 61724411637264> file.info(tmp.csv)$size / 1024^2 # размер файла в мегабайтах#> [1] 580.866077</nowiki>}}
Теперь мы можем сравнить производительность функции <code>read.table()</code> с параметрами по умолчанию и парамтерами, рекомендованными для увеличения производительности данной функции. Для этого нам понадобится пакет {{r-package|microbenchmark}}.
 
{{r-code|code=<nowiki>microbenchmark(defaults = read.csv(tmp.csv, header = TRUE),
ompimize = read.csv(tmp.csv, header = TRUE, nrows = n.rows, comment.char = "",
colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")))
#> Unit: milliseconds
#> expr min lq median uq max neval
#> defaults 26.34 26.73 27.00 27.35 30.35 100
#> ompimize 18.46 18.55 18.63 18.73 19.09 100
</nowiki>}}
 
Отметим, что чтение файла осуществлялось непосредственно из оперативной памяти, т.к. файл находился в файловой системе [<code>tmpfs</code>](http://ru.wikipedia.org/wiki/Tmpfs).
 
Значения для аргумента <code>colClasses</code> мы получили ранее с помощью команды <code>sapply(data.df, class)</code>.
 
Обратим внимание на то, что результат работы сравниваемых вариантов функции <code>read.table()</code> абсолютно идентичен.
 
{{r-code|code=<nowiki>identical(read.csv(tmp.csv, header = TRUE),
read.csv(tmp.csv, header = TRUE, nrows = n.rows, comment.char = "",
colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")))
#> [1] TRUE
</nowiki>}}
 
Таким образом, по результатам сравнения, можем заключить, что указание специфических аргументов функции <code>read.table()</code> позволяет существенно ускорить процесс импорта данных в формате CSV.
 
=== Функция <code>scan</code> ===
 
Проведя [[R:Профилирование кода|профилирование]] выполнения функции <code>read.table()</code>, мы получили следующие результаты:
 
{{r-code|code=<nowiki>tmp.log <- tempfile(pattern = "prof-", fileext = ".log")
source("http://git.psylab.info/r-scripts/raw/master/proftable.R")
Rprof(tmp.log, interval = 0.01)
data.df <- read.csv(tmp.csv, header = TRUE)
Rprof(NULL)
proftable(tmp.log)
#> Calls:
#> RealTime PctTime Call
#> 0.04 80 scan
#> 0.01 20 type.convert > .External2
#>
#> Files: None
#>
#> Parent Call: knit > process_file > withCallingHandlers > process_group > process_group.block > call_block > block_exec > in_dir > evaluate > evaluate_call > handle > withCallingHandlers > withVisible > eval > eval > read.csv > read.table
#>
#> Total Time: 0.05 seconds
#>
#> Percent of run time represented: 100%
</nowiki>}}
 
В данном выводе хорошо видно, что большую часть времени затрачено на чтение файла с помощью функции <code>scan()</code>. Мы задались вопросом: а возможно ли получить результат, идентичный функции <code>read.table()</code>, пользуясь только функцией <code>scan()</code> и приведёт ли это к увеличению производительности. После некоторых экспериментов нам подобрать команду, приводящую к нужному результату:
 
}}{r eval = FALSE}
data.frame(scan(tmp.csv, what = list(a = integer(0), b = integer(0), c = character(0), d = numeric(0), e = numeric(0), f = numeric(0)),
nmax = n.rows, sep = ",", quote = "\"", multi.line = FALSE, skip = 1L, quiet = TRUE))
}}
 
Поясним некоторые моменты:
 
* <code>skip = 1L</code>: пропускаем первую строку, т.к. она содержит названия столбцов и при её обработка функция <code>scan()</code> выдаст ошибку;
* <code>what = list(a = integer(0), b = integer(0), c = character(0), d = numeric(0), e = numeric(0), f = numeric(0))</code>; аналог аргумента <code>colClasses</code> для <code>read.table()</code>;
* <code>nmax = n.rows</code>: аналог <code>nrows</code> для <code>read.table()</code>.
 
Ниже приведены результаты сравнения работы функций <code>read.table()</code> и <code>scan()</code>:
 
{{r-code|code=<nowiki>microbenchmark(defaults = read.csv(tmp.csv, header = TRUE),
ompimize = read.csv(tmp.csv, header = TRUE, nrows = n.rows, comment.char = "",
colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")),
scan = data.frame(scan(tmp.csv, nmax = n.rows, sep = ",", quote = "\"", multi.line = FALSE, skip = 1L, quiet = TRUE,
what = list(a = integer(0), b = integer(0), c = character(0), d = numeric(0), e = numeric(0), f = numeric(0)))))
#> Unit: milliseconds
#> expr min lq median uq max neval
#> defaults 26.86 27.25 27.46 27.63 30.91 100
#> ompimize 18.78 18.96 19.08 19.17 22.10 100
#> scan 18.90 19.16 19.26 19.34 25.63 100
</nowiki>}}
 
Как видим, функций <code>scan()</code> работает быстрее <code>read.table()</code> с параметрами по умолчанию и может сравниться по скорости работы с <code>read.table()</code> с оптимизированными аргументами.
 
=== Функция <code>fread</code> ===
 
Пакет {{r-package|data.table}}, помимо прочего, включает в себя функцию для чтения csv-файлов - <code>fread()</code>. Стоит отметить, что полученная в результате импорта переменная будет иметь класс <code>data.table</code>, что предполагает определённую специфику работы с ней<ref>Синтаксис работы с классом <code>data.table</code> отличен от синтаксиса работы с матрицами и таблицами данных в R.</ref>.
 
Сравним результаты работы функций <code>read.table()</code> и <code>fread()</code>:
 
{{r-code|code=<nowiki>microbenchmark(defaults = read.csv(tmp.csv, header = TRUE),
ompimize = read.csv(tmp.csv, header = TRUE, nrows = n.rows, comment.char = "",
colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")),
fread = fread(tmp.csv))
#> Unit: milliseconds
#> expr min lq median uq max neval
#> defaults 26.922 27.605 27.77 28.166 30.482 100
#> ompimize 18.771 18.964 19.06 19.194 19.898 100
#> fread 5.854 5.952 6.02 6.123 6.479 100
</nowiki>}}
 
Таким образом, функция <code>fread()</code> является более быстрым и более удобным (за счёт автоматического определения входных параметров) инструментом по сравнению со штатной функцией <code>read.table()</code> (даже при использовании оптимальных параметров).
 
Приведём ещё один интересный факт. Попытка оптимизировать аргументы для функции <code>fread()</code> по аналогии с <code>read.table()</code> не привела к каким либо значимым изменениям, что свидетельствует о высокой оптимизации алгоритмов, задействованных в функции <code>fread()</code>.
 
{{r-code|code=<nowiki>microbenchmark(defaults = fread(tmp.csv),
ompimize = fread(tmp.csv, sep = ",", header = TRUE, nrows = n.rows,
colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")))
#> Unit: milliseconds
#> expr min lq median uq max neval
#> defaults 5.798 5.896 5.992 6.064 6.245 100
#> ompimize 5.800 5.877 5.937 6.040 7.221 100
</nowiki>}}
 
== Формат RData/RDS ==
 
Форматы RData/Rds являются внутренними форматами в R. Данные форматы характеризуются тем, что имеют двоичный вид и предполагают сжатие данных. Хотя CSV являются наиболее универсальным форматом, для работы с данными в R предпочтительнее использовать внутренние форматы R.
 
Сравним скорость записи данных в формат CSV в двоичный формат RData:
 
{{r-code|code=<nowiki>tmp.csv <- tempfile(fileext = "csv")
tmp.RDS <- tempfile(fileext = "RDS")
microbenchmark(text = write.csv(data.df, tmp.csv, row.names = FALSE),
binary = saveRDS(data.df, tmp.RDS))
#> Unit: milliseconds
#> expr min lq median uq max neval
#> text 44.40 44.72 44.79 44.93 45.96 100
#> binary 21.14 21.33 21.43 21.56 21.99 100
</nowiki>}}
 
Удостоверимся в идентичности исходных и импортированных данных:
 
{{r-code|code=<nowiki>identical(read.csv(tmp.csv, header = TRUE),
readRDS(tmp.RDS))
#> [1] TRUE
</nowiki>}}
 
Размеры полученных файлов в мегабайтах:
 
{{r-code|code=<nowiki>file.info(tmp.csv)$size / 1024^2
#> [1] 0.6077
file.info(tmp.RDS)$size / 1024^2
#> [1] 0.2552
</nowiki>}}
 
Теперь сравним скорость чтения данных с помощью функций <code>read.table()</code>, <code>fread()</code> и <code>readRDS()</code>:
 
{{r-code|code=<nowiki>microbenchmark(defaults = read.csv(tmp.csv, header = TRUE),
ompimize = read.csv(tmp.csv, header = TRUE, nrows = n.rows, comment.char = "",
colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")),
fread = fread(tmp.csv),
readRDS = readRDS(tmp.RDS))
#> Unit: milliseconds
#> expr min lq median uq max neval
#> defaults 27.074 27.482 27.663 27.939 29.320 100
#> ompimize 18.783 19.014 19.089 19.222 20.867 100
#> fread 5.834 5.965 6.026 6.116 7.519 100
#> readRDS 1.920 1.966 1.983 2.014 3.400 100
</nowiki>}}
 
Отметим, что запись и чтение производилась в файловую систему [<code>tmpfs</code>](http://ru.wikipedia.org/wiki/Tmpfs), размещённую непосредственно в оперативной памяти.
 
Таким образом, среди преимуществ форматов RData/RDS можно выделить:
 
* Скорость записи превышает скорость записи в текстовый формат;
* Скорость чтения значительно превышает скорость чтения текстового формата;
* Размер полученного файла значительного меньше текстового формата<ref>Начиная с версии 2.10 функция <code>read.table()</code> может также читать сжатые gz, xz, bzip файлы. Но даже при максимальной степени сжатия размер сжатого csv-файла едва приближается к размеру RData-файла. При этом скорость чтения сжатого csv-файла приблизительно такая же как при чтении несжатого csv-файла.</ref>;
* Возможность сохранять любые объекты: функции, векторы, таблицы данных, матрицы, списки.
 
Среди минусов можно отметить:
 
* Возможность использования данного формата исключительно в среде R.
 
== Примечания ==
 
<references />
 
[[Категория:R]]
[[Категория:Оптимизация кода]]