Чтение TSV с определенной кодировкой (начальные два байта и UTF-8 после) и NUL после каждого символа

У меня есть неясный TSV, который я пытаюсь прочитать, и, по-видимому, он начинается с идентификатора и имеет некоторые встроенные значения NUL (кажется, что это один NUL после каждого подлинного символа). Это первые 100 байт файла (сокращенные с помощью шестнадцатеричного редактора): test_file .txt (мне пришлось переименовать его в txt, чтобы загрузить, но это файл tsv).

К сожалению, я не могу прочитать его ни базовыми функциями, ни readr, ни data.table.

Вот репрекс:

file <- 'test_file.txt'

# read.tsv is not able to read the file since there are embedded NULs
tmp <- read.table(file, header = T, nrows = 2)
#> Warning in read.table(file, header = T, nrows = 2): line 1 appears to
#> contain embedded nulls
#> Warning in read.table(file, header = T, nrows = 2): line 2 appears to
#> contain embedded nulls
#> Warning in read.table(file, header = T, nrows = 2): line 3 appears to
#> contain embedded nulls
#> Warning in scan(file = file, what = what, sep = sep, quote = quote, dec =
#> dec, : embedded nul(s) found in input

# unfortunately the skipNul argument also doesn't work
tmp <- read.table(file, header = T, nrows = 2, skipNul = T)
#> Error in read.table(file, header = T, nrows = 2, skipNul = T): more columns than column names

# read_tsv from readr is also not able to read the file (probably since it stops each line after a NUL)
tmp <- readr::read_tsv(file, n_max = 2)
#> Warning: Duplicated column names deduplicated: '' => '_1' [3], '' =>
#> '_2' [4], '' => '_3' [5], '' => '_4' [6], '' => '_5' [7], '' => '_6' [8],
#> '' => '_7' [9], '' => '_8' [10], '' => '_9' [11], '' => '_10' [12], '' =>
#> '_11' [13]
#> Parsed with column specification:
#> cols(
#>   y = col_character(),
#>   col_character(),
#>   `_1` = col_character(),
#>   `_2` = col_character(),
#>   `_3` = col_character(),
#>   `_4` = col_character(),
#>   `_5` = col_character(),
#>   `_6` = col_character(),
#>   `_7` = col_character(),
#>   `_8` = col_character(),
#>   `_9` = col_character(),
#>   `_10` = col_character(),
#>   `_11` = col_character()
#> )
#> Error in read_tokens_(data, tokenizer, col_specs, col_names, locale_, : Column 2 must be named

# fread from data.table is also not able to read the file (although it is the first function that more clearly shows the problem)
tmp <- data.table::fread(file, nrows = 2)
#> Error in data.table::fread(file, nrows = 2): embedded nul in string: 'ÿþy\0e\0a\0r\0'

# read lines reads the first actual character 'y' and the file identifier characters that seem to parse as 'ÿþ' in UTF-8
readLines(file, n = 1)
#> Warning in readLines(file, n = 1): line 1 appears to contain an embedded
#> nul
#> [1] "ÿþy"

# the problem is in the hidden NUL characters as the following command shows
readLines(file, n = 1, skipNul = T)
#> [1] "ÿþyear\tmonth\tday\tDateTime\tAreaTypeCode\tAreaName\tMapCode\tPowerSystemResourceName\tProductionTypeName\tActualGenerationOutput\tActualConsumption\tInstalledGenCapacity\tSubmissionTS"

Есть ли обходной путь, который позволяет мне читать этот файл? Желательно не с помощью базовой функции, так как они невероятно медленные, и мне приходится читать несколько файлов (> 20) размером более 300 МБ.


person takje    schedule 09.01.2018    source источник
comment
Можете ли вы загрузить свой файл как суть?   -  person MichaelChirico    schedule 09.01.2018
comment
Вы имеете в виду что-то кроме ссылки на txt и код, включенный в выпуск?   -  person takje    schedule 09.01.2018
comment
Да, я не уверен, что загрузка в GH не изменила необработанное содержимое файла. Я более уверен, что копирование в Gist не будет иметь такого эффекта (в частности, я получаю другую ошибку, чем вы)   -  person MichaelChirico    schedule 09.01.2018
comment
@MichaelChirico Что мне копировать? Шестнадцатеричный дамп? Потому что, как только я копирую части файла из текстового редактора, он, кажется, меняет кодировку, которая решает проблему.   -  person takje    schedule 09.01.2018
comment
@MichaelChirico В качестве еще одного теста я открыл загруженный файл, и он оказался таким же, как исходный файл (при открытии в шестнадцатеричном редакторе).   -  person takje    schedule 09.01.2018
comment
связывание проблемы GH для data.table; я согласен что это баг   -  person MichaelChirico    schedule 09.01.2018
comment
Рассмотрите возможность использования подхода, упомянутого здесь, в качестве пережитка: stackoverflow.com /вопросы/34214859/   -  person MichaelChirico    schedule 09.01.2018


Ответы (1)


Текущий обходной путь описан в разделе Удаление символов NUL (в R).

Этот ответ в значительной степени зависит от этого. Я добавил еще несколько комментариев, адаптировал пример так, чтобы он также работал с байтами заголовка, и добавил использование fread (data.table) и read_tsv (readr) для создания окончательной ссылки на фрейм данных.

file <- 'test_file.txt'

# read the file as raw and skip the first two header bytes
data_raw <- readBin(file, raw(), file.info(file)$size)[3:file.info(file)$size]

# replace the NUL values by an uncommon UTF-8 character so that we can
# later filter this one out. Please check out this list for more uncommon
# characters: http://www.fileformat.info/info/charset/UTF-8/list.htm
data_raw[data_raw == as.raw(0)] <- as.raw(1)

# convert to a string and remove the replaced characters (raw(1) in our case)
data_string <- gsub("\001", "", rawToChar(data_raw), fixed = TRUE)

# convert the resulting string to a data frame by a function to your liking
data_tmp1 <- data.table::fread(data_string, header = T) # quickest
data_tmp2 <- readr::read_tsv(data_string) # slower and is not working well with the UTF-8 characters
data_tmp3 <- read.table(data_string) # crashed R for my files (probably due to size)

Этот подход был протестирован на относительно больших файлах (350 МБ). Первый шаг (readBin) самый медленный, но для такого размера файла он занимает всего около 30 секунд. Скорость, вероятно, также зависит от вашего жесткого диска.

Для действительно больших файлов также может быть проблема с памятью. В отличие от fread, все будет считано в память перед выполнением какой-либо обработки.

person takje    schedule 10.01.2018