Изменения

Перейти к: навигация, поиск

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

1775 байтов добавлено, 14:45, 28 мая 2014
м
Загрузка 2814-import-data.wiki
<!-- R:Оптимизация/Импорт данных -->
 
 
 
{{CC-BY-4.0|author=автором Артём Клевцов}}
 
{{Pkg-req-notice}}
 
При обработке данных большого объёма имеет смысл импортировать только ту часть данных, которая непосредственно участвует в обработке. Это целесообразно как с точки зрения расхода оперативной памяти, так и скорости выполнения операций поиска, сортировки и фильтрации данных.
Создадим таблицу данных содержащую <math>10^{6}</math> строк и 6 столбцов:
{{r-code|code=<nowiki>> set.seed(123) # начальная точка для генератора случайных чисел для воспроизводимости результатов> N n.rows <- 10^6L 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 288 604 B 311 J -1.4534 1073538 88.38 54507 187.73#> 2 789 242 C 325 D -10.4479 5794 82.32 94997 167.26#> 3 409 65 J 871 E -0.4182 1138610 108.11 48970 640.97#> 4 884 700 J -329 B 0.9194 989727 107.54 82477 962.64#> 5 941 594 D 126 I 0.7360 1226191 103.02 48312 663.5#> 6 46 843 I -0357 C 1.8354 3854 8492.24 32275 306.74</nowiki>}}
Структура данных:
{{r-code|code=<nowiki>> str(DFdata.df)#> 'data.frame': 1000000 10000 obs. of 6 variables:#> $ a: int 288 789 409 884 941 46 529 893 552 457 ...#> $ b: int 604 242 65 700 594 843 746 303 416 862 311 325 871 329 126 357 931 876 821 22 ...#> $ c: Factor w/ 10 levels "A","B","C","D",..: 2 3 10 10 4 5 2 9 10 10 7 4 3 3 6 6 5 ...#> $ e: num 1.453 -1.448 354 -0.418 579 -0.919 861 0.736 973 0.619 ...#> $ d: num 10788.4 821 83 108.3 1137 107.8 103.1 98.5 122 ...#> $ f: num 546 949 490 825 484 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.csv2csv(DFdata.df, tmp.csv, row.names = FALSE)</nowiki>}}
Размер полученного файла составил:
{{r-code|code=<nowiki>> file.info(tmp.csv)$size # размер файла в байтах#> [1] 63724802637264> file.info(tmp.csv)$size / 1024^2 # размер файла в мегабайтах#> [1] 600.776077</nowiki>}}
Теперь мы можем сравнить производительность функции <code>read.table()</code> с параметрами по умолчанию и парамтерами, рекомендованными для увеличения производительности данной функции. Для этого нам понадобится пакет {{r-package|microbenchmark}}.
{{r-code|code=<nowiki>> microbenchmark(defaults = read.csv2csv(tmp.csv, header = TRUE),+ ompimize = read.csv2csv(tmp.csv, header = TRUE, nrows = Nn.rows, comment.char = "", + colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")))#> Unit: secondsmilliseconds#> expr min lq median uq max neval#> defaults 526.205 734 26.124 73 727.453 700 27.847 1135 30.968 35 100#> ompimize 218.836 246 18.859 55 218.874 263 18.895 373 19.303 09 100</nowiki>}}
Отметим, что чтение файла осуществлялось непосредственно из оперативной памяти, т.к. файл находился в файловой системе [<code>tmpfs</code>](http://ru.wikipedia.org/wiki/Tmpfs <code>tmpfs</code>]).
Значения для аргумента <code>colClasses</code> мы получили ранее с помощью команды <code>sapply(DFdata.df, class)</code>.
Обратим внимание на то, что результат работы сравниваемых вариантов функции <code>read.table()</code> абсолютно идентичен.
{{r-code|code=<nowiki>> identical(read.csv2csv(tmp.csv, header = TRUE),+ read.csv2csv(tmp.csv, header = TRUE, nrows = Nn.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)> DF data.df <- read.tablecsv(tmp.csv, sep = ";", header = TRUE)> Rprof(NULL)> proftable(tmp.log) #> Calls:#> RealTime PctTime Call #> 860.520 04 80 scan #> 130.235 01 20 type.convert > .External2 0.245 #> #> Files:None /tmp/RtmpAvhCfO/file23b65745e7ba.R#> #> 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>}}
Total TimeВ данном выводе хорошо видно, что большую часть времени затрачено на чтение файла с помощью функции <code>scan()</code>. Мы задались вопросом: 4а возможно ли получить результат, идентичный функции <code>read.08 secondstable()</code>, пользуясь только функцией <code>scan()</code> и приведёт ли это к увеличению производительности. После некоторых экспериментов нам подобрать команду, приводящую к нужному результату:
Percent of run time represented: 100%</nowiki>}} В данном выводе хорошо видно, что большую часть времени затрачено на чтение файла с помощью функции <code>scan()</code>. Мы задались вопросом: а возможно ли получить результат, идентичный функции <code>read.table()</code>, пользуясь только функцией и <code>scan()</code> и приведёт ли это к увеличению производительности. После некоторых экспериментов нам подобрать команду, приводящую к нужному результату: {{r-code|codeeval =<nowiki>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 = Nn.rows, sep = ";", dec = ",", quote = "\"", multi.line = FALSE, skip = 1L, quiet = TRUE))</nowiki>}}
Поясним некоторые моменты:
* <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 = Nn.rows</code>: аналог <code>nrows</code> для <code>read.table()</code>.
Ниже приведены результаты сравнения работы функций <code>read.table()</code> и <code>scan()</code>:
{{r-code|code=<nowiki>> microbenchmark(defaults = read.csv2csv(tmp.csv, header = TRUE),+ ompimize = read.csv2csv(tmp.csv, header = TRUE, nrows = Nn.rows, comment.char = "",+ colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")),+ scan = data.frame(scan(tmp.csv, nmax = Nn.rows, sep = ";", dec = ",", 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: secondsmilliseconds#> expr min lq median uq uq max neval#> defaults 426.726 786 27.121 25 827.481 1046 27.729 1163 30.719 91 100#> ompimize 218.835 278 18.858 96 219.871 208 19.898 317 22.261 10 100#> scan 218.915 290 19.960 16 219.971 226 19.993 334 25.413 63 100</nowiki>}}
Как видим, функций <code>scan()</code> работает быстрее <code>read.table()</code> с параметрами по умолчанию и может сравниться по скорости работы с <code>read.table()</code> с оптимизированными аргументами.
Сравним результаты работы функций <code>read.table()</code> и <code>fread()</code>:
{{r-code|code=<nowiki>> library(data.table)> microbenchmark(defaults = read.csv(tmp.csv, header = TRUE),+ ompimize = read.csv(tmp.csv, header = TRUE, nrows = Nn.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 395726.5 4049922 27.0 4066 4166605 27.77 28.166 30.3 4797 482 100#> ompimize 284418.9 2853771 18.6 2928 2932964 19.06 19.194 19.0 3557 898 100#> fread 5325.2 854 5335.4 534 952 6.02 5356.5 123 616 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 RDS ==
Форматы RData/Rds являются внутренними форматами в R. Данные форматы характеризуются тем, что имеют двоичный вид и предполагают сжатие данных. Хотя CSV являются наиболее универсальным форматом, для работы с данными в R предпочтительнее использовать внутренние форматы R.
Сравним скорость записи данных в формат CSV в двоичный формат RData:
{{r-code|code=<nowiki>> tmp.csv <- tempfile(fileext = "csv")> tmp.RData RDS <- tempfile(fileext = "RDataRDS")> microbenchmark(text = write.tablecsv(DFdata.df, file = tmp.csv, sep = ";", row.names = FALSE),+ binary = savesaveRDS(DFdata.df, file = tmp.RDataRDS))#> Unit: secondsmilliseconds#> expr min lq median uq max neval#> text 444.658 440 44.737 72 444.755 479 44.825 593 45.526 96 100#> binary 221.202 214 21.203 33 221.205 243 21.210 256 21.248 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.RDatacsv)$size / 1024^2#> [1] 250.6077> file.info(tmp.csvRDS)$size / 1024^2#> [1] 600.772552</nowiki>}}
Теперь сравним скорость чтения данных с помощью функций <code>read.table()</code>, <code>fread()</code> и <code>loadreadRDS()</code>:
{{r-code|code=<nowiki>> microbenchmark(defaults = read.tablecsv(tmp.csv, sep = ";", header = TRUE),+ ompimize = read.tablecsv(tmp.csv, sep = ";", header = TRUE, nrows = Nn.rows, comment.char = "",+ colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")),+ fread = fread(tmp.csv),+ load readRDS = loadreadRDS(tmp.RDataRDS))#> Unit: milliseconds#> expr min lq median uq max neval#> defaults 394027.6 4032074 27.5 4067482 27.2 4155663 27.4 4903939 29.4 320 100#> ompimize 284718.8 2923783 19.3 2929014 19.5 2941089 19.6 3632222 20.5 867 100#> fread 5335.1 834 5345.965 6 .026 5356.1 116 5367.3 1220.1 519 100 load #> readRDS 177.1 .920 1771.2 966 1771.2 983 1772.6 014 2683.6 400 100</nowiki>}}
Отметим, что запись и чтение производилась в файловую систему [<code>tmpfs</code>](http://ru.wikipedia.org/wiki/Tmpfs tmpfs]), размещённую непосредственно в оперативной памяти.
Таким образом, среди преимуществ форматов RData/Rds RDS можно выделить;:
* Скорость записи превышает скорость записи в текстовый формат;
* Скорость чтения значительно превышает скорость чтения текстового формата;
* Размер полученного файла значительного меньше текстового формата<ref>Начиная с версии 2.10 функция <code>read.table()</code> может также читать сжатые gz, xz, bzip файлы. Но даже при максимальной степени сжатия размер сжатого csv-файла едва приближается к размеру RData-файла. При этом скорость чтения сжатого csv-файла приблизтельно приблизительно такая же как при чтении несжатого csv-файла.</ref>;* Возможность сохранять любые объекты: функции, векторы, таблицы данных, матрицы, списки.
Среди минусов модно можно отметить:
* Возможность использования данного формата исключительно в среде R.

Навигация