Изменения

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

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

2886 байтов добавлено, 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.tablecsv(DFdata.df, tmp.csv, sep = ";", row.names = 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.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")))#> Unit: secondsmilliseconds#> expr min lq median uq max neval#> defaults 326.951 434 26.043 73 427.068 400 27.169 435 30.850 35 100#> ompimize 218.852 246 18.927 55 218.931 263 18.943 373 19.631 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.tablecsv(tmp.csv, sep = ";", header = TRUE),+ read.tablecsv(tmp.csv, sep header = TRUE, nrows = n.rows, comment.char = ";", header = TRUE,+ colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric"),+ nrows = N, comment.char = ""))#> [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 #> #> 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.245 05 seconds#> #> Percent of run time represented: 100%</nowiki>}}
FilesВ данном выводе хорошо видно, что большую часть времени затрачено на чтение файла с помощью функции <code>scan()</code>. Мы задались вопросом:а возможно ли получить результат, идентичный функции <code>read.table()</tmpcode>, пользуясь только функцией <code>scan()</RtmpAvhCfO/file23b65745e7bacode> и приведёт ли это к увеличению производительности.RПосле некоторых экспериментов нам подобрать команду, приводящую к нужному результату:
Parent Call: read.table  Total Time: 4.08 seconds 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 = ";,", 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.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")),+ scan = data.frame(scan(tmp.csv, nmax = Nn.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: secondsmilliseconds#> expr min lq median uq max neval#> defaults 326.948 486 27.037 25 427.109 446 27.220 463 30.757 91 100#> ompimize 218.851 278 18.863 96 219.931 308 19.005 317 22.686 10 100#> scan 218.970 290 19.985 16 319.058 326 19.653 334 25.822 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.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))#> 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>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, <code>fread()xz, bzip файлы. Но даже при максимальной степени сжатия размер сжатого csv-файла едва приближается к размеру RData-файла. При этом скорость чтения сжатого csv-файла приблизительно такая же как при чтении несжатого csv-файла.</code> и <code>load()</coderef>;* Возможность сохранять любые объекты:функции, векторы, таблицы данных, матрицы, списки.
{{r-code|code=<nowiki>> microbenchmark(defaults = read.table(tmp.csv, sep = ";", header = TRUE),+ ompimize = read.table(tmp.csv, sep = ";", header = TRUE, nrows = N, comment.char = "",+ colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")),+ fread = fread(tmp.csv),+ load = load(tmp.RData))UnitСреди минусов можно отметить: milliseconds expr min lq median uq max neval defaults 3940.6 4032.5 4067.2 4155.4 4903.4 100 ompimize 2847.8 2923.3 2929.5 2941.6 3632.5 100 fread 533.1 534.6 535.1 536.3 1220.1 100 load 177.1 177.2 177.2 177.6 268.6 100</nowiki>}}
Отметим, что запись и чтение производилась * Возможность использования данного формата исключительно в файловую систему [http://ru.wikipedia.org/wiki/Tmpfs tmpfs], размещённую непосредственно в оперативной памятисреде R.
== Примечания ==

Навигация