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

Материал Psylab.info - энциклопедии психодиагностики
Перейти к: навигация, поиск
м (Функция read.table())
м
Строка 3: Строка 3:
 
== Импорт CSV ==
 
== Импорт CSV ==
  
=== Функция <code>read.table()</code> ===
+
=== Функция <code>read.table</code> ===
  
 
Для импорта файлов CSV в R предусмотрена функция <code>read.table()</code> и функци-обёртка (wrapper) <code>read.csv()</code>. С точки зрения скорости работы, параметры функции <code>read.table()</code>, заданные по умолчанию, не являются оптимальными. Приведём несколько рекомендаций по использованию функции <code>read.table()</code>:
 
Для импорта файлов CSV в R предусмотрена функция <code>read.table()</code> и функци-обёртка (wrapper) <code>read.csv()</code>. С точки зрения скорости работы, параметры функции <code>read.table()</code>, заданные по умолчанию, не являются оптимальными. Приведём несколько рекомендаций по использованию функции <code>read.table()</code>:
Строка 13: Строка 13:
 
Создадим таблицу данных содержащую <math>10^{6}</math> строк и 6 столбцов:
 
Создадим таблицу данных содержащую <math>10^{6}</math> строк и 6 столбцов:
  
{{r-code|code=<nowiki>> N <- 10^6L # задаём количество наблюдений
+
{{r-code|code=<nowiki>>set.seed(123) # начальная точка для генератора случайных чисел для воспроизводимости результатов
 +
> N <- 10^6L # задаём количество наблюдений
 
> DF <- data.frame(a = sample(1:10^3L, N, replace = TRUE),
 
> DF <- data.frame(a = sample(1:10^3L, N, replace = TRUE),
 
+                  b = sample(1:10^3L, N, replace = TRUE),
 
+                  b = sample(1:10^3L, N, replace = TRUE),
Строка 70: Строка 71:
  
 
{{r-code|code=<nowiki>> microbenchmark(defaults = read.table(tmp.csv, sep = ";", header = TRUE),
 
{{r-code|code=<nowiki>> microbenchmark(defaults = read.table(tmp.csv, sep = ";", header = TRUE),
+                ompimize = 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"),
+
+                                      colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")))
+                                      nrows = N, comment.char = ""))
+
 
Unit: seconds
 
Unit: seconds
     expr  min    lq median    uq   max neval
+
     expr  min    lq median    uq   max neval
  defaults 6.204 6.673 7.024 7.253 10.996   100
+
  defaults 3.951 4.043 4.068 4.169 4.850   100
  ompimize 2.833 2.858 2.921 3.161  3.202   100</nowiki>}}
+
  ompimize 2.852 2.927 2.931 2.943 3.631   100</nowiki>}}
 +
 
 +
Отметим, что чтение файла осуществлялось непосредственно из оперативной памяти, т.к. файл находился в файловой системе [http://ru.wikipedia.org/wiki/Tmpfs <code>tmpfs</code>].
  
 
Значения для аргумента <code>colClasses</code> мы получили ранее с помощью команды <code>sapply(DF, class)</code>.
 
Значения для аргумента <code>colClasses</code> мы получили ранее с помощью команды <code>sapply(DF, class)</code>.
Строка 90: Строка 92:
 
Таким образом, по результатам сравнения, можем заключить, что указание специфических аргументов функции <code>read.table()</code> позволяет существенно ускорить процесс импорта данных в формате CSV.
 
Таким образом, по результатам сравнения, можем заключить, что указание специфических аргументов функции <code>read.table()</code> позволяет существенно ускорить процесс импорта данных в формате CSV.
  
=== Функция <code>scan()</code> ===
+
=== Функция <code>scan</code> ===
 +
 
 +
Проведя [[R:Профилирование кода|профилирование]] выполнения функции <code>read.table()</code> мы получили следующие результаты:
 +
 
 +
{{r-code|code=<code>> 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                   
 +
90.86  scan                   
 +
  7.59  type.convert > .External2
 +
  1.55                           
 +
 
 +
Files:
 +
/tmp/RtmpAvhCfO/file23b65745e7ba.R
 +
 
 +
Parent Call: read.table
 +
 
 +
Total Time: 7.11 seconds
 +
 
 +
Percent of run time represented: 100%</code>}}
 +
 
 +
В данном выводе хорошо видно, что 90% времени затрачено на чтение файла с помощью функции <code>scan()</code>. Мы задались вопросом: а возможно ли получить требуемый результат, пользуясь только функцией и <code>scan()</code> и приведёт ли это к увеличению производительности. После некоторых экспериментов нам подобрать команду, приводящую к нужному результату:
 +
 
 +
{{r-code|code=<code>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))</code>}}
 +
 
 +
Ниже приведены результаты сравнения работы функций <code>read.table()</code> и <code>scan()</code>:
 +
 
 +
{{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")),
 +
+                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</nowiki>}}
 +
 
 +
Как видим, функций <code>scan()</code> работает быстрее <code>read.table()</code> с параметрами по умолчанию и может сравниться по скорости работы с <code>read.table()</code> с оптимизированными аргументами.
  
=== Функция <code>fread()</code> ===
+
=== Функция <code>fread</code> ===
  
 
Пакет {{r-package|data.table}}, помимо прочего, включает в себя функцию для чтения csv-файлов - <code>fread()</code>. Стоит отметить, что полученная в результате импорта переменная будет иметь класс <code>data.table</code>, что предполагает определённую специфику работы с ней<ref>Синтаксис работы с классом <code>data.table</code> отличен от синтаксиса работы с матрицами и таблицами данных в R.</ref>.
 
Пакет {{r-package|data.table}}, помимо прочего, включает в себя функцию для чтения csv-файлов - <code>fread()</code>. Стоит отметить, что полученная в результате импорта переменная будет иметь класс <code>data.table</code>, что предполагает определённую специфику работы с ней<ref>Синтаксис работы с классом <code>data.table</code> отличен от синтаксиса работы с матрицами и таблицами данных в R.</ref>.
Строка 100: Строка 145:
 
{{r-code|code=<nowiki>> library(data.table)
 
{{r-code|code=<nowiki>> library(data.table)
 
> microbenchmark(defaults = read.table(tmp.csv, sep = ";", header = TRUE),
 
> microbenchmark(defaults = read.table(tmp.csv, sep = ";", header = TRUE),
+                ompimize = 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"),
+
+                                      colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric")),
+                                      nrows = N, comment.char = "", quote=""),
+
+                fread = fread(tmp.csv))
+                data.table = fread(tmp.csv))
+
 
Unit: milliseconds
 
Unit: milliseconds
      expr    min    lq median    uq     max neval
+
    expr    min    lq median    uq max neval
  defaults 5344.4 8063.6 8513.6 8853.0 13602.0   100
+
defaults 3957.5 4049.0   4066 4166.3 4797   100
  ompimize 2839.3 2856.9 2896.7 3203.7  3573.5   100
+
ompimize 2844.9 2853.6  2928 2932.0 3557   100
  data.table 526.6  528.2 535.9 578.2  932.3   100</nowiki>}}
+
    fread 532.2 533.4    534 535.5 616   100</nowiki>}}
  
 
Таким образом, функция <code>fread()</code> является более быстрым и более удобным (за счёт автоматического определения входных параметров) инструментом по сравнению со штатной функцией <code>read.table()</code> (даже при использовании оптимальных параметров).
 
Таким образом, функция <code>fread()</code> является более быстрым и более удобным (за счёт автоматического определения входных параметров) инструментом по сравнению со штатной функцией <code>read.table()</code> (даже при использовании оптимальных параметров).

Версия 16:35, 6 апреля 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.table(DF, tmp.csv, sep = ";", row.names = FALSE)</syntaxhighlight>

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

КодR

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

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

Код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"))) 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.table(tmp.csv, sep = ";", header = TRUE), + read.table(tmp.csv, sep = ";", header = TRUE, + colClasses = c("integer", "integer", "factor", "numeric", "numeric", "numeric"), + nrows = N, comment.char = "")) [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                     
90.86   scan                     
 7.59   type.convert > .External2
 1.55                            

Files: /tmp/RtmpAvhCfO/file23b65745e7ba.R

Parent Call: read.table

Total Time: 7.11 seconds

Percent of run time represented: 100%</syntaxhighlight>

В данном выводе хорошо видно, что 90% времени затрачено на чтение файла с помощью функции scan(). Мы задались вопросом: а возможно ли получить требуемый результат, пользуясь только функцией и 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>

Ниже приведены результаты сравнения работы функций 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() (даже при использовании оптимальных параметров).

Примечания

  1. Синтаксис работы с классом data.table отличен от синтаксиса работы с матрицами и таблицами данных в R.