Mar 6: data.table pt.4
Чтение и запись текстовых файлов
В работе аналитики чаще всего сталкиваются с данными, которые хранятся в простом текстовом формате (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
, необходимо их отдельно преобразовать
Импортируем файл:
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-координатах (буквы для колонок и цифры для строк). К сожалению, на данный момент нельзя указать сразу несколько диапазонов. Во-вторых, в прочитанном диапазоне, если не указать обратное, первая строка будет по умолчанию интерпретироваться как строка заголовка. В результате приходится либо прямо задавать названия колонок, либо указывать, что заголовка нет:
## # 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. Решение должно уметь импортировать не только этот конкретный файл, но и другие файлы с такими особенностями. Перед импортом можете скачать файл и импортировать его локально (это рекомендация, не более).