R:Оптимизация/Импорт данных — различия между версиями

Материал Psylab.info - энциклопедии психодиагностики
Перейти к: навигация, поиск
м (Формат RData/Rds)
м (Функция read.table)
Строка 59: Строка 59:
  
 
{{r-code|code=<nowiki>> tmp.csv <- tempfile(fileext=".csv") # генерируем имя и путь для временного файла
 
{{r-code|code=<nowiki>> tmp.csv <- tempfile(fileext=".csv") # генерируем имя и путь для временного файла
> write.table(DF, tmp.csv, sep = ";", row.names = FALSE)</nowiki>}}
+
> write.csv2(DF, tmp.csv, row.names = FALSE)</nowiki>}}
  
 
Размер полученного файла составил:
 
Размер полученного файла составил:
Строка 70: Строка 70:
 
Теперь мы можем сравнить производительность функции <code>read.table()</code> с параметрами по умолчанию и парамтерами, рекомендованными для увеличения производительности данной функции. Для этого нам понадобится пакет {{r-package|microbenchmark}}.
 
Теперь мы можем сравнить производительность функции <code>read.table()</code> с параметрами по умолчанию и парамтерами, рекомендованными для увеличения производительности данной функции. Для этого нам понадобится пакет {{r-package|microbenchmark}}.
  
{{r-code|code=<nowiki>> microbenchmark(defaults = read.table(tmp.csv, sep = ";", header = TRUE),
+
{{r-code|code=<nowiki>> microbenchmark(defaults = read.csv2(tmp.csv, header = TRUE),
+                ompimize = read.table(tmp.csv, sep = ";", header = TRUE, nrows = N, comment.char = "",  
+
+                ompimize = read.csv2(tmp.csv, header = TRUE, nrows = N, comment.char = "",  
+                                     colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")))
+
+                                     colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")))
 
Unit: seconds
 
Unit: seconds
 
     expr  min    lq median    uq  max neval
 
     expr  min    lq median    uq  max neval
Строка 84: Строка 84:
 
Обратим внимание на то, что результат работы сравниваемых вариантов функции <code>read.table()</code> абсолютно идентичен.
 
Обратим внимание на то, что результат работы сравниваемых вариантов функции <code>read.table()</code> абсолютно идентичен.
  
{{r-code|code=<nowiki>> identical(read.table(tmp.csv, sep = ";", header = TRUE),
+
{{r-code|code=<nowiki>> identical(read.csv2(tmp.csv, header = TRUE),
+          read.table(tmp.csv, sep = ";", header = TRUE,
+
+          read.csv2(tmp.csv, header = TRUE, nrows = N, comment.char = "",
+                     colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric"),
+
+                     colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")))
+                      nrows = N, comment.char = ""))
+
 
[1] TRUE</nowiki>}}
 
[1] TRUE</nowiki>}}
  

Версия 12:08, 7 апреля 2014

При обработке данных большого объёма имеет смысл импортировать только ту часть данных, которая непосредственно участвует в обработке. Это целесообразно как с точки зрения расхода оперативной памяти, так и скорости выполнения операций поиска, сортировки и фильтрации данных.

Формат CSV

Функция read.table

Для импорта файлов CSV в R предусмотрена функция read.table() и функци-обёртка (wrapper) read.csv(). С точки зрения скорости работы, параметры функции read.table(), заданные по умолчанию, не являются оптимальными. Приведём несколько рекомендаций по использованию функции read.table():

  • указать тип переменных, содержащихся в таблице с помощью аргумента colClasses;
  • указать количество импортируемых строк с помощью аргумента nrows;
  • отключить поиск комментариев с помощью аргумента comment.char = "".

Создадим таблицу данных содержащую [math]10^{6}[/math] строк и 6 столбцов:

КодR

<syntaxhighlight lang="r">> set.seed(123) # начальная точка для генератора случайных чисел для воспроизводимости результатов > N <- 10^6L # задаём количество наблюдений > DF <- data.frame(a = sample(1:10^3L, N, replace = TRUE), + b = sample(1:10^3L, N, replace = TRUE), + c = sample(LETTERS[1:10], N, replace = TRUE), + e = rnorm(N), + d = rnorm(N, 100, 15), + f = runif(N, 0, 10^3L)) </syntaxhighlight>

Рассмотрим таблицу более подробно. Начало таблицы:

КодR

<syntaxhighlight lang="r">> head(DF) a b c e d f 1 288 604 B 1.4534 107.38 545.7 2 789 242 C -1.4479 82.32 949.2 3 409 65 J 0.4182 113.11 489.9 4 884 700 J -0.9194 98.54 824.6 5 941 594 D 0.7360 122.02 483.5 6 46 843 I -0.8354 84.24 322.7</syntaxhighlight>

Структура данных:

КодR

<syntaxhighlight lang="r">> str(DF) 'data.frame': 1000000 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 ... $ c: Factor w/ 10 levels "A","B","C","D",..: 2 3 10 10 4 9 10 10 7 4 ... $ e: num 1.453 -1.448 0.418 -0.919 0.736 ... $ d: num 107.4 82.3 113.1 98.5 122 ... $ f: num 546 949 490 825 484 ...</syntaxhighlight>

Типы переменных:

КодR

<syntaxhighlight lang="r">> sapply(DF, class) a b c e d f "integer" "integer" "factor" "numeric" "numeric" "numeric"</syntaxhighlight>

Размер объекта в оперативной памяти:

КодR

<syntaxhighlight lang="r">> print(object.size(DF), units = "auto") 34.3 Mb</syntaxhighlight>

Сохраним таблицу в csv-файл следующими командами:

КодR

<syntaxhighlight lang="r">> tmp.csv <- tempfile(fileext=".csv") # генерируем имя и путь для временного файла > write.csv2(DF, tmp.csv, row.names = FALSE)</syntaxhighlight>

Размер полученного файла составил:

КодR

<syntaxhighlight lang="r">> file.info(tmp.csv)$size # размер файла в байтах [1] 63724802 > file.info(tmp.csv)$size / 1024^2 # размер файла в мегабайтах [1] 60.77</syntaxhighlight>

Теперь мы можем сравнить производительность функции read.table() с параметрами по умолчанию и парамтерами, рекомендованными для увеличения производительности данной функции. Для этого нам понадобится пакет microbenchmark.

КодR

<syntaxhighlight lang="r">> microbenchmark(defaults = read.csv2(tmp.csv, header = TRUE), + ompimize = read.csv2(tmp.csv, header = TRUE, nrows = N, comment.char = "", + colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric"))) Unit: seconds expr min lq median uq max neval defaults 3.951 4.043 4.068 4.169 4.850 100 ompimize 2.852 2.927 2.931 2.943 3.631 100</syntaxhighlight>

Отметим, что чтение файла осуществлялось непосредственно из оперативной памяти, т.к. файл находился в файловой системе tmpfs.

Значения для аргумента colClasses мы получили ранее с помощью команды sapply(DF, class).

Обратим внимание на то, что результат работы сравниваемых вариантов функции read.table() абсолютно идентичен.

КодR

<syntaxhighlight lang="r">> identical(read.csv2(tmp.csv, header = TRUE), + read.csv2(tmp.csv, header = TRUE, nrows = N, comment.char = "", + colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric"))) [1] TRUE</syntaxhighlight>

Таким образом, по результатам сравнения, можем заключить, что указание специфических аргументов функции read.table() позволяет существенно ускорить процесс импорта данных в формате CSV.

Функция scan

Проведя профилирование выполнения функции read.table() мы получили следующие результаты:

КодR

<syntaxhighlight lang="r">> 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 <- read.table(tmp.csv, sep = ";", header = TRUE) > Rprof(NULL) > proftable(tmp.log) Calls: PctTime Call 86.520 scan 13.235 type.convert > .External2 0.245 Files: /tmp/RtmpAvhCfO/file23b65745e7ba.R Parent Call: read.table Total Time: 4.08 seconds Percent of run time represented: 100%</syntaxhighlight>

В данном выводе хорошо видно, что большую часть времени затрачено на чтение файла с помощью функции scan(). Мы задались вопросом: а возможно ли получить результат, идентичный функции read.table(), пользуясь только функцией и scan() и приведёт ли это к увеличению производительности. После некоторых экспериментов нам подобрать команду, приводящую к нужному результату:

КодR

<syntaxhighlight lang="r">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, sep = ";", quote = "\"", multi.line = FALSE, skip = 1L, quiet = TRUE))</syntaxhighlight>

Поясним некоторые моменты:

  • skip = 1L: пропускаем первую строку, т.к. она содержит названия столбцов и при её обработка функция scan() выдаст ошибку;
  • what = list(a = integer(0), b = integer(0), c = character(0), d = numeric(0), e = numeric(0), f = numeric(0)); аналог аргумента colClasses для read.table();
  • nmax = N: аналог nrows для read.table().

Ниже приведены результаты сравнения работы функций read.table() и scan():

КодR

<syntaxhighlight lang="r">> 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")), + scan = data.frame(scan(tmp.csv, nmax = N, 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: seconds expr min lq median uq max neval defaults 3.948 4.037 4.109 4.220 4.757 100 ompimize 2.851 2.863 2.931 3.005 3.686 100 scan 2.970 2.985 3.058 3.653 3.822 100</syntaxhighlight>

Как видим, функций scan() работает быстрее read.table() с параметрами по умолчанию и может сравниться по скорости работы с read.table() с оптимизированными аргументами.

Функция fread

Пакет data.table, помимо прочего, включает в себя функцию для чтения csv-файлов - fread(). Стоит отметить, что полученная в результате импорта переменная будет иметь класс data.table, что предполагает определённую специфику работы с ней[1].

Сравним результаты работы функций read.table() и fread():

КодR

<syntaxhighlight lang="r">> library(data.table) > 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)) Unit: milliseconds expr min lq median uq max neval defaults 3957.5 4049.0 4066 4166.3 4797 100 ompimize 2844.9 2853.6 2928 2932.0 3557 100 fread 532.2 533.4 534 535.5 616 100</syntaxhighlight>

Таким образом, функция fread() является более быстрым и более удобным (за счёт автоматического определения входных параметров) инструментом по сравнению со штатной функцией read.table() (даже при использовании оптимальных параметров).

Формат RData/Rds

Форматы RData/Rds являются внутренними форматами в R. Данные форматы характеризуются тем, что имеют двоичный вид и предполагают сжатие данных. Хотя CSV являются наиболее универсальным форматом, для работы с данными в R предпочтительнее использовать внутренние форматы R.

Сравним скорость записи данных в формат CSV в двоичный формат RData:

КодR

<syntaxhighlight lang="r">> tmp.csv <- tempfile(fileext = "csv") > tmp.RData <- tempfile(fileext = "RData") > microbenchmark(text = write.table(DF, file = tmp.csv, sep = ";", row.names = FALSE), + binary = save(DF, file = tmp.RData)) Unit: seconds expr min lq median uq max neval text 4.658 4.737 4.755 4.825 5.526 100 binary 2.202 2.203 2.205 2.210 2.248 100</syntaxhighlight>

Размеры полученных файлов в мегабайтах:

КодR

<syntaxhighlight lang="r">> file.info(tmp.RData)$size / 1024^2 [1] 25 > file.info(tmp.csv)$size / 1024^2 [1] 60.77</syntaxhighlight>

Теперь сравним скорость чтения данных с помощью функций read.table(), fread() и load():

КодR

<syntaxhighlight lang="r">> 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</syntaxhighlight>

Отметим, что запись и чтение производилась в файловую систему tmpfs, размещённую непосредственно в оперативной памяти.

Таким образом, среди преимуществ форматов RData/Rds можно выделить;

  • Скорость записи превышает скорость записи в текстовый формат;
  • Скорость чтения значительно превышает скорость чтения текстового формата;
  • Размер полученного файла значительного меньше текстового формата[2].

Среди минусов модно отметить:

  • Возможность использования данного формата исключительно в среде R.

Примечания

  1. Синтаксис работы с классом data.table отличен от синтаксиса работы с матрицами и таблицами данных в R.
  2. Начиная с версии 2.10 функция read.table() может также читать сжатые gz, xz, bzip файлы. Но даже при максимальной степени сжатия размер сжатого csv-файла едва приближается к размеру RData-файла. При этом скорость чтения сжатого csv-файла приблизтельно такая же как при чтении несжатого csv-файла.