Mar 6: data.table pt.4

Запись занятия

Запись занятия 6 марта:

Все записи организованы в плейлист


Чтение и запись текстовых файлов

В работе аналитики чаще всего сталкиваются с данными, которые хранятся в простом текстовом формате (txt, разделители строк \n, \r или \n\r ) и основанном на них табличных форматах csv (с разделителями , или ;) или tsv (\t).


Построчное чтение

При работе с текстами проще всего читать файлы построчно — весь текст импортируется как вектор строковых значений, а элементы этого вектора - блоки текста, ограниченные переносами строк. Это удобно при импорте текстов, в которых есть смысловые блоки, как в художественных и поэтических текстах. Точно также построчное чтение может быть использовано для импорта файлов с ошибками или даже, в особо экзотических случаях, для чтения скриптов.

Для построчного чтения обычно используется функция readLines() базового пакета или аналогичные ей. С помощью аргументов функции можно указать, сколько необходимо прочитать строк, задать кодировку и что делать, если в файле меньше строк, чем было задано.

Прочитаем первые несколько строк текста песни:

txt_lines <- readLines('./data/oxxy_gorgorod.txt', n = 5)
txt_lines
## [1] "####"                                    
## [2] "Незаметно поправь её"                    
## [3] "Одеяло, за это себя предавая анафеме"    
## [4] "Она вышла из пены"                       
## [5] "Худой отпечаток плеча оставляя на кафеле"


Чтение текстовых файлов: read.table() и fread()

Для чтения текстовых файлов, которые содержат в табличном формате, в базовом R есть функция read.table() и функции-обертки, которые обращаются к ней, но с другими значениями аргументов по умолчанию (read.csv(), read.csv2, read.delim(), read.delim2()). Табличный формат предполагает наличие строк и колонок в файле, выделенные разделителями строк и полей соответственно, при этом формат файла может быть как txt, так и csv.

Несмотря на всю видимую простоту, при импорте табличных форматов можно столкнуться с очень большим количеством нетривиальных проблем. В немалой части это можно назвать следствием их широкой распространенности: практически все современные текстовые процессоры умеют работать с csv-файлами, экспорт из баз данных также нередко делается в csv. Ко всему прочему, текстовые форматы хорошо сжимаются при архивации.

К наиболее частым сложностям, которые возникают при импорте текстовых файлов можно отнести следующие:

  • неожиданные разделители (например, экспорт из MS Excel в csv создает файл с разделителем ;);
  • лишние или пропущенные разделители строк или колонок (\t\t вместо \t), что создает разное количество колонок в таблице;
  • несоответствие файла расширению или вообще отсутствие расширения;
  • нестандартные кодировки, в том числе проблемы их импорта при работе в Windows;
  • наличие embedded nuls (\0) или метки порядка байтов (bite order marks, BOM);
  • наличие символов """", //// и прочих технических символов;
  • мета-данные (запись о дате и источнике данных) в первых строках файла.

Большинство этих наюнсов решается при настройке параметров импорта с помощью аргументов функций импорта (т. е., настройки по умолчанию не справляются). В частности, в функциях чтения таблиц можно задать разделители полей и десятичные разделители (sep и dec), кодировку файла и отображения, обработку пустых строк и т. д. Некоторые проблемы, например, импорт данных с embedded nuls постепенно решаются в новых версиях функций и пакетов. Помимо этих аргументов также очень полезны аргументы, которые позволяют прямо указать, сколько строк импортировать (или пропустить от начала), какие типы данных в колонках и какие колонки стоит пропустить, а так же — надо ли конвертировать в факторы строковые значения.

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


Запись данных в текстовые файлы: write.table() и fwrite()

Запись данных в текстовые форматы также может вестись как построчно, так и сразу всей таблицей. Для записи построчно обычно используется функция writeLines().

Запись таблиц происходит аналогичным образом, с помощью функции write.table() или её аналогом в других пакетах (в частности, более быстрый вариант data.table::fwrite()). С помощью аргументов можно задать как типы разделителей (строк, полей и десятичные разделители), так и кодировку. Обычно рекомендуется выставлять аргумент row.names = FALSE, чтобы в файл не были записаны отдельной колонкой названия (номера) строк.


Чтение файлов MS Excel

В отличие от простых форматов типа .csv, файлы, созданные в MS Excel импортировать не так просто. Ситуация также осложняется тем, что форматы .xls и .xlsx сильно различаются по внутренней структуре. Часть пакетов, которые обеспечивают взаимодействие с Excel-файлами, требуют установки языка Java (java development kit) в систему и соответствующих R-пакетов (rJava, в частности), самые часто используемые пакеты тут XLconnect и xlsx. Тем не менее, в большинстве случаев необходим только прочитать файл, и тут могут быть полезны пакеты пакеты openxlsx и readxl, не требующие установки jdk. Пакет openxlsx к тому же умеет еще и записывать файлы в .xlsx, как XLconnect, в том числе и с условным форматированием ячеек.


Чтение файлов

Пакет readxl, по наблюдениям, чуть быстрее читает файлы, чем openxlsx или пакеты, требующие Java, поэтому рассмотрим здесь его. В целом, функционал пакетов достаточно схож и то, что может readxl, практически всегда можно реализовать и в других пакетах, и наоборот.

Основные функции пакета - read_xls() для чтения файлов MS Excel, созданных в версиях до MS Excel 2007, read_xlsx() - для чтения файлов, созданных в более современных версиях, также есть обобщающая функция read_excel(), которая по расширению файла определяет, какую из этих функций надо использовать. Пакет readxl принадлежит к экосистеме tideverse, поэтому импортированные таблицы имеют классы tibble и data.frame. Соответственно, если работать в data.table, необходимо их отдельно преобразовать

Вот так выглядит исходный файл в MS Excel:

Импортируем файл:

library(readxl)
# читаем файл
path <- './data/xls_example.xlsx'
xlsx_data <- read_xlsx(path)

str(xlsx_data)
## tibble [7 × 5] (S3: tbl_df/tbl/data.frame)
##  $ company  : chr [1:7] "xxx" "xxx" "xxx" "yyy" ...
##  $ item_code: num [1:7] 20000559 20000779 13117 40000289 40000274 ...
##  $ barcode  : num [1:7] 4.60e+12 4.60e+12 4.61e+12 3.57e+12 3.57e+12 ...
##  $ amount   : num [1:7] 3 4 2 6 2 1 9
##  $ price    : num [1:7] 480.3 26.9 353.8 207.6 174.8 ...
xlsx_data
## # A tibble: 7 × 5
##   company item_code       barcode amount price
##   <chr>       <dbl>         <dbl>  <dbl> <dbl>
## 1 xxx      20000559 4601026307584      3 480. 
## 2 xxx      20000779 4603182002259      4  27.0
## 3 xxx         13117 4607072020156      2 354. 
## 4 yyy      40000289 3574661287522      6 208. 
## 5 yyy      40000274 3574661287539      2 175. 
## 6 zzz      10000581 4602196002682      1 537. 
## 7 zzz      20000458 4601026308567      9 371.

При импорте можно указать тип колонок. Делается это с помощью аргумента col_types, в который необходимо передать вектор такой же длины, сколько колонок, состоящий из значений logical, numeric, date, text или list. К сожалению, в отличие от схожего аргумента colClasses в функциях импорта csv/xlsx-файлов (read.table(), read.csv(), data.table::fread(), openxlsx::read.xlsx и проч.), в readxl названия типов не соответствуют названиям атомарных типов данных в R.

Если для col_types указать NULL или guess, то тип будет выбран на основе анализа минимум 1000 строк (или всех строк, если датасет меньше 1000 строк). Также можно указать вектор типов длиной не по количеству колонок, а только из одного значения, то все колонки будут импортированы в этом типе. Значение skip указывает, что эту колонку не надо импортировать.

# читаем файл с указанием типа text для всех колонок
xlsx_data <- read_xlsx(path, col_types = 'text')
xlsx_data
## # A tibble: 7 × 5
##   company item_code barcode       amount price 
##   <chr>   <chr>     <chr>         <chr>  <chr> 
## 1 xxx     20000559  4601026307584 3      480.26
## 2 xxx     20000779  4603182002259 4      26.95 
## 3 xxx     13117     4607072020156 2      353.76
## 4 yyy     40000289  3574661287522 6      207.55
## 5 yyy     40000274  3574661287539 2      174.81
## 6 zzz     10000581  4602196002682 1      537.46
## 7 zzz     20000458  4601026308567 9      370.7


Частичное чтение файла

Большинство xlsx-файлов содержит несколько непустых листов. По умолчанию функции импорта читают только первый лист, остальные же игнорируются. Пакеты типа XLConnect могут читать весь файл в отдельный объект. Для чтения названий листов xlsx-файла можно воспользоваться функцией readxl::excel_sheets() и полученный вектор потом циклом или через lapply() передать в функцию импорта:

# читаем список листов файла
sheets <- readxl::excel_sheets(path)
sheets
## [1] "Sheet1" "Sheet2"
# читаем все листы в список и смотрим структуру
file_str <- lapply(sheets, function(x) read_xlsx(path, sheet = x))
str(file_str)
## List of 2
##  $ : tibble [7 × 5] (S3: tbl_df/tbl/data.frame)
##   ..$ company  : chr [1:7] "xxx" "xxx" "xxx" "yyy" ...
##   ..$ item_code: num [1:7] 20000559 20000779 13117 40000289 40000274 ...
##   ..$ barcode  : num [1:7] 4.60e+12 4.60e+12 4.61e+12 3.57e+12 3.57e+12 ...
##   ..$ amount   : num [1:7] 3 4 2 6 2 1 9
##   ..$ price    : num [1:7] 480.3 26.9 353.8 207.6 174.8 ...
##  $ : tibble [3 × 2] (S3: tbl_df/tbl/data.frame)
##   ..$ company: chr [1:3] "xxx" "yyy" "zzz"
##   ..$ items  : num [1:3] 198 256 32

В некоторых случаях требуется импортировать только определенный диапазон ячеек со всего листа: например, когда на созданном вручную листе есть и таблица с данными, и графики, и дополнительные материалы. Для чтения определенной ячейки или диапазона ячеек используют аргумент range, в котором в строковом виде указывают диапазон ячеек в Excel-координатах (буквы для колонок и цифры для строк). К сожалению, на данный момент нельзя указать сразу несколько диапазонов. Во-вторых, в прочитанном диапазоне, если не указать обратное, первая строка будет по умолчанию интерпретироваться как строка заголовка. В результате приходится либо прямо задавать названия колонок, либо указывать, что заголовка нет:

read_xlsx(path, range = 'A5:B6', col_names = c('company', 'item_code'))
## # A tibble: 2 × 2
##   company item_code
##   <chr>       <dbl>
## 1 yyy      40000289
## 2 yyy      40000274


Домашнее задание

Датасеты

level 1 (IATYTD)

У вас есть xlsx-файл по популяции и видам пингвинов. Импортируйте данные из этого файла в R. При работе постарайтесь обойтись только средствами R, не открывая файл в Excel.


level 2 (HNTR)

  • У вас есть данные Tate Gallery по художникам и их произведениям. Импортируйте файл по художникам, ссылки на файлы:
url_artwork <- 'https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-01-12/artwork.csv'
url_artists <- "https://github.com/tategallery/collection/raw/master/artist_data.csv"
  • С помощью функции setnames() пакета data.table (или names() из базового пакета, если вам так удобнее) переименуйте в файле по художникам колонки yearOfBirth в year_of_birth, колонки yearOFDeath, placeOfBirth и placeOfDeath аналогично.

  • посчитайте, сколько художников из списка живы в настоящий момент.

  • посчитайте, сколько среди живущих в настоящий момент художников мужчин и сколько женщин.

## [1] 1304
##    gender   N
## 1: Female 297
## 2:   Male 944
## 3:         63


level 3 (HMP)

  • подумайте и покажите решение, как можно узнать структуру файла artwork, однако полностью импортировать вы его не можете (предположим, что он настолько большой, что повесит систему намертво).

  • импортируйте только те колонки, которые позволят вам идентифицировать художника, название работы и ее формат (колонка medium).

  • посчитайте, сколько в галерее работ от художников-мужчин и сколько от женщин. Отдельной колонкой выведите статистики по живущим на данный момент художникам.

  • посчитайте, сколько работ разных форматов сделал каждый художник. Вам потребуется функция dcast().


level 4 (UV)

Импортируйте файл txt_example.txt. Перед импортом можете скачать файл и импортировать его локально (это рекомендация, не более).

##     КодПодр       КодНом
## 1  69500044 ЛН-008560521
## 2  69500044    ЛН-745621
## 3  69500300  69506432458
## 4  69500346 695089466518
## 5  69500268  69504851543
## 6  69500009  69506584554
## 7  69500058  ЛН-86600383
## 8  69500147  ЛН-41200521
## 9  69500147  ЛН-05860521
## 10 69500268  ЛН-00000927


level 5 (N)

Импортируйте файл extr’version.xls. Решение должно уметь импортировать не только этот конкретный файл, но и другие файлы с такими особенностями. Перед импортом можете скачать файл и импортировать его локально (это рекомендация, не более).