R(ファイル・ディレクトリ)

2024年2月22日 (木)

[R]ヌル(0x00)を含むテキストファイルを高速に読み込む

始めにヌルを含む巨大なテキストファイルを作成する。以下は文字コードがUTF-8の環境のため(3×5+2)×2×10^7=340,000,000バイト(約324MB)のテキストファイルを作成している。ファイルの行数は10^7=10,000,000行。それぞれの行は苗字と名前の間にヌル(0x00)が挟まれている。

> Sys.getlocale()
[1] "LC_COLLATE=Japanese_Japan.utf8;LC_CTYPE=Japanese_Japan.utf8;LC_MONETARY=Japanese_Japan.utf8;LC_NUMERIC=C;LC_TIME=Japanese_Japan.utf8"
> ra1 <- unlist(iconv("石見", toRaw = TRUE))
> ra2 <- unlist(iconv("舞菜香", toRaw = TRUE))
> ra3 <- unlist(iconv("和多田", toRaw = TRUE))
> ra4 <- unlist(iconv("美咲", toRaw = TRUE))
> ra <- c(ra1, as.raw(0), ra2, as.raw(0x0a), ra3, as.raw(0), ra4, as.raw(0x0a))
> writeBin(rep(ra, 10 ^ 7), "temp.txt")
> file.info(dir(patter = "temp\\.txt"))["size"]
size
temp.txt 3.4e+08

scan関数を使用して読み込む場合は、skipNulオプションにTRUEを指定しないとうまく読み込むことができない。

> lns <- scan("temp.txt", what = character(), sep = "\n", quiet = TRUE)
警告メッセージ:
scan("temp.txt", what = character(), sep = "\n", quiet = TRUE) で:
入力文字列の中に nul が埋め込まれています
> length(lns)
[1] 20000000
> lns[1:3]
[1] "石見" "和多田" "石見"
> lns <- scan("temp.txt", what = character(), sep = "\n", quiet = TRUE, skipNul = TRUE)
> length(lns)
[1] 20000000
> lns[1:3]
[1] "石見舞菜香" "和多田美咲" "石見舞菜香"

read.table関数を使用して読み込む場合も、skipNulオプションにTRUEを指定しないとうまく読み込むことができない。

> dtf <- read.table("temp.txt", header = FALSE, sep = "\n")
警告メッセージ:
1: read.table("temp.txt", header = FALSE, sep = "\n") で:
line 1 appears to contain embedded nulls
2: read.table("temp.txt", header = FALSE, sep = "\n") で:
line 2 appears to contain embedded nulls
(表示省略)
入力文字列の中に nul が埋め込まれています
> nrow(dtf)
[1] 20000000
> head(dtf, 3)
V1
1 石見
2 和多田
3 石見
> dtf <- read.table("temp.txt", header = FALSE, sep = "\n", skipNul = TRUE)
> nrow(dtf)
[1] 20000000
> head(dtf, 3)
V1
1 石見舞菜香
2 和多田美咲
3 石見舞菜香

それぞれの関数で読み込みに要する時間を計測してみる。

> system.time(
+ scan("temp.txt", what = character(), sep = "\n", quiet = TRUE, skipNul = TRUE)
+ )
ユーザ システム 経過
3.06 0.14 3.86
> system.time(
+ scan("temp.txt", what = character(), sep = "\n", quiet = TRUE, skipNul = TRUE)
+ )
ユーザ システム 経過
3.35 0.14 3.89
> system.time(
+ read.table("temp.txt", header = FALSE, sep = "\n", skipNul = TRUE)
+ )
ユーザ システム 経過
3.06 0.09 4.03
> system.time(
+ read.table("temp.txt", header = FALSE, sep = "\n", skipNul = TRUE)
+ )
ユーザ システム 経過
2.92 0.20 4.06

scan関数の方が若干早い。なお、単純にテキストファイルを読み込むだけであればreadrパッケージのfread関数やread_lines関数のほうが高速に動作するが、以下の例のとおりにヌルを含むとうまく動作をしない。これを制御するオプションは無いようだ。

> library(readr)
> fread("temp.txt", sep = "\n")
fread("temp.txt", sep = "\n") でエラー:
文字列の中に nul が埋め込まれています: '石見\0舞菜香'
追加情報: 警告メッセージ:
fread("temp.txt", sep = "\n") で:
Previous fread() session was not cleaned up properly. Cleaned up ok at the beginning of this fread() call.
> read_lines("temp.txt", progress = FALSE)
character(0)

2024年1月27日 (土)

[R]ヌル(NULL)が含まれるテキストファイルをread.table関数で読み込む

read.table関数は、読み込むファイルにヌル(NULL、0x00)が含まれていると、列内のヌル以降は読み込まなくなる。これをヌルは無視してとにかく読み込むようにするには、skipNulオプションにTRUEを指定する。以下は、3行からなるテキストファイルtemp.txtを作成し、そのファイルを読み込んだ例。3行目の「c」の次にはヌルを含んでおり、デフォルトでは警告が発生しているが、skipNulオプションにTRUEを指定すると、ヌルを無視してすべて読み込んでいることがわかる。

> Sys.getlocale()
[1] "LC_COLLATE=Japanese_Japan.utf8;LC_CTYPE=Japanese_Japan.utf8;LC_MONETARY=Japanese_Japan.utf8;LC_NUMERIC=C;LC_TIME=Japanese_Japan.utf8"
> ch1 <- c(0x30:0x39, 0x0d, 0x0a, 0x41:0x5a, 0x0d, 0x0a)
> ch2 <- c(0x61:0x63, 0x00, 0x65:0x7a, 0x0d, 0x0a)
> ra <- as.raw(c(ch1, ch2))
> writeBin(ra, "temp.txt")
> read.table("temp.txt")
V1
1 0123456789
2 ABCDEFGHIJKLMNOPQRSTUVWXYZ
3 abc
警告メッセージ:
read.table("temp.txt") で: line 3 appears to contain embedded nulls
> read.table("temp.txt", skipNul = TRUE)
V1
1 0123456789
2 ABCDEFGHIJKLMNOPQRSTUVWXYZ
3 abcefghijklmnopqrstuvwxyz

2024年1月21日 (日)

[R]fwriteによる日付時刻型の出力

data.tableパッケージのfwrite関数では、日付時刻型のオブジェクトを出力するとUTCに変換して出力されてしまう(ちょうど9時間前の日付時刻が出力される)。以下の例のとおり、出力元のオブジェクトのタイムゾーンをJSTに設定しても、出力はUTCの日付時刻になり、それを表す記号(Z)が付けられて出力される。オブジェクトの値をそのまま(JSTのまま)出力したい場合は、dateTimeAsオプション(デフォルトは「ISO」)に「write.csv」を指定すると、よく見る書式でかつJSTで出力される。

「write.csv」を指定したときのファイルはExcelで開くと、その列はそのまま日付時刻型の値になる便利な書式である。

> library(data.table)
> library(lubridate)
> n <- 3
> no <- 1:n
> dtm <- make_datetime(2000, 1:n, 1, 2, 3, 4, "Asia/Tokyo")
> dtf <- data.frame(no, name, dtm)
> print(dtf)
no name dtm
1 1 January 2000-01-01 02:03:04
2 2 February 2000-02-01 02:03:04
3 3 March 2000-03-01 02:03:04
> fwrite(dtf, "temp.csv", sep = ",")
> shell("type temp.csv")
no,name,dtm
1,January,1999-12-31T17:03:04Z
2,February,2000-01-31T17:03:04Z
3,March,2000-02-29T17:03:04Z
> fwrite(dtf, "temp.csv", sep = ",", dateTimeAs = "ISO")
> shell("type temp.csv")
no,name,dtm
1,January,1999-12-31T17:03:04Z
2,February,2000-01-31T17:03:04Z
3,March,2000-02-29T17:03:04Z
> fwrite(dtf, "temp.csv", sep = ",", dateTimeAs = "squash")
> shell("type temp.csv")
no,name,dtm
1,January,19991231170304000
2,February,20000131170304000
3,March,20000229170304000
> fwrite(dtf, "temp.csv", sep = ",", dateTimeAs = "write.csv")
> shell("type temp.csv")
no,name,dtm
1,January,2000-01-01 02:03:04
2,February,2000-02-01 02:03:04
3,March,2000-03-01 02:03:04

2024年1月16日 (火)

[R]バイナリファイルを作成する

数値型ベクトルでバイト列を作成し、それをロウ型に変換してwriteBin関数で出力すればよい。以下は、ch1とch2の2つの数値型ベクトルでバイト列を作成し、それを純粋にバイト単位でファイル出力した例。

> ch1 <- c(0x30:0x39, 0x0d, 0x0a, 0x41:0x5a, 0x0d, 0x0a, 0x61:0x7a, 0x0d, 0x0a)
> ch2 <- c(0xe3, 0x81, 0x82, 0xe3, 0x81, 0x84, 0xe3, 0x81, 0x86, 0x0d, 0x0a)
> ra <- as.raw(c(ch1, ch2))
> writeBin(ra, "temp.txt")

temp.txtを画面にダンプした結果は以下のとおり。

PS > Format-Hex .\temp.txt
パス: ○○○
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000 30 31 32 33 34 35 36 37 38 39 0D 0A 41 42 43 44 0123456789..ABCD
00000010 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 EFGHIJKLMNOPQRST
00000020 55 56 57 58 59 5A 0D 0A 61 62 63 64 65 66 67 68 UVWXYZ..abcdefgh
00000030 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 ijklmnopqrstuvwx
00000040 79 7A 0D 0A E3 81 82 E3 81 84 E3 81 86 0D 0A yz..ããã..

temp.txtをテキストエディタで文字コードをUTF-8に指定して開くと、以下のようになるはず。

0123456789
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
あいう

本例では、結果をわかりやすくするためUTF-8の文字コードに相当する範囲で出力したのであって、そうではないバイト列(ヌル等)でも問題なく出力することができる。

2024年1月 7日 (日)

[R]ヌル(0x00)を含むCSVファイルやTSVファイルを読み込む

始めにヌルを含むCSVファイルを作成する。

> ch1 <- c(0x41:0x43, 0x2c, 0x00, 0x2c, 0x47:0x49, 0x0d, 0x0a)
> ch1 <- c(0x41:0x43, 0x2c, 0x00, 0x2c, 0x47:0x49, 0x0d, 0x0a)
> ch2 <- c(0x61, 0x00, 0x63, 0x2c, 0x64:0x66, 0x2c, 0x67:0x69, 0x0d, 0x0a)
> ra <- as.raw(c(ch1, ch2))
> writeBin(ra, "temp.csv")

ファイルをメモ帳で開くと以下のようになる。1行目の2列目は値自体がヌルで、2行目の1列は「a」と「c」の間は空白(0x20)ではなくヌル(0x00)である。

ABC, ,GHI
a c,def,ghi

標準で搭載されているread.tableは、skipNulオプションにTRUEを指定しないと、ヌルだけの列は列とは認識されず、行によってフィールド数が異なることになるため、エラーが発生して読み込みに失敗する。skipNulオプションにTRUEを指定と、ヌルは完全に無視して他はすべて読み込まれる。ヌルの次の「c」もきちんと読み込まれている。

> dtf <- read.table("temp.csv", header = FALSE, sep = ",")
scan(file = file, what = what, sep = sep, quote = quote, dec = dec, でエラー:
line 2 did not have 2 elements
追加情報: 警告メッセージ:
1: read.table("temp.csv", header = FALSE, sep = ",") で:
line 1 appears to contain embedded nulls
2: read.table("temp.csv", header = FALSE, sep = ",") で:
line 2 appears to contain embedded nulls
> print(dtf)
エラー: オブジェクト 'dtf' がありません
> dtf <- read.table("temp.csv", header = FALSE, sep = ",", skipNul = TRUE)
> print(dtf)
V1 V2 V3
1 ABC GHI
2 ac def ghi

readrパッケージのread_delim関数を試してみる。ヌルだけの列はきちんと処理されているようだが、ヌルを含む列は、ヌル以降は読み込まれていない(「c」が表示されない)。

> library(readr)
> tib <- read_delim("temp.csv", delim = ",", col_names = FALSE, progress = FALSE, show_col_types = FALSE)
警告メッセージ:
One or more parsing issues, call `problems()` on your data frame for details, e.g.:
dat <- vroom(...)
problems(dat)
> print(data.frame(tib))
X1 X2 X3
1 ABC GHI
2 a def ghi

data.tableパッケージのfread関数を使う。これはヌルを完全に無視して読み込むし、ヌルだけの列は空欄(NA)ということで処理できているし、ヌル以降の文字もきちんと読み込まれている。

> library(data.table)
> dtt <- fread("temp.csv", header = FALSE, sep = ",", showProgress = FALSE)
> print(dtt)
V1 V2 V3
1: ABC GHI
2: ac def ghi

2024年1月 1日 (月)

[R]ファイルへの高速な書き込み

data.tableパッケージのfwrite関数を使う。以下は、行数が100万のデータフレームを、write.table関数とfwrite関数を使ってそれぞれTSV形式のテキストファイルに書き込んだ例。書き込んだ行数はヘッダー行も含むため100万1行であることに注意。それぞれ2回繰り返し行ったが、fwrite関数による書き込みのほうが10倍以上速いことがわかる。

> library(data.table)
> n <- 10 ^ 6
> no <- 1:n
> s <- c("カナメ", "フレイア", "美雲", "マキナ", "レイナ")
> name <- sample(s, n, replace = TRUE)
> shoe_size <- round(rnorm(n, 23.5, 1), 1)
> dtf <- data.frame(no, name, shoe_size)
> head(dtf, 3)
no name shoe_size
1 1 美雲 22.6
2 2 マキナ 22.5
3 3 レイナ 23.5
> system.time(
+ write.table(dtf, "temp.tsv", sep = "\t", row.name = FALSE, quote = FALSE)
+ )
ユーザ システム 経過
3.48 0.18 3.71
> system.time(
+ write.table(dtf, "temp.tsv", sep = "\t", row.name = FALSE, quote = FALSE)
+ )
ユーザ システム 経過
3.45 0.09 3.54
> system.time(
+ fwrite(dtf, "temp.tsv", sep = "\t", row.name = FALSE, quote = FALSE)
+ )
ユーザ システム 経過
0.07 0.02 0.04
> system.time(
+ fwrite(dtf, "temp.tsv", sep = "\t", row.name = FALSE, quote = FALSE)
+ )
ユーザ システム 経過
0.16 0.00 0.05

2023年9月10日 (日)

[R]様々な文字コードのCSVファイルを読み込む

read.table関数を使う。fileEncodingオプションに文字コードを指定する。

動作確認のため、最初にPowerShellを使ってカレントディレクトリに、順番にシフトJIS、UTF-8(BOM無し)、UTF-8(BOM付き)、UTF-16(ビッグエンディアン、BOM付き)、UTF-16(リトルエンディアン、BOM付き)のCSVファイルを出力する。PowerShellのコマンドレットでは、Unicode系は原則BOM付きとなる。そのため、BOM無しのUTF-8の出力には、FileクラスのWriteAllLinesメソッドを使用している。

PS > $lines = "1,ABC", "2,abc", "3,あいう"
PS > $odir = (gl).Path
PS > $lines | Out-File -Encoding default ($odir + "\sjis.csv")
PS > [IO.File]::WriteAllLines(($odir + "\utf8nb.csv"), $lines)
PS > $lines | Out-File -Encoding utf8 ($odir + "\utf8wb.csv")
PS > $lines | Out-File -Encoding bigendianunicode ($odir + "\utf16bewb.csv")
PS > $lines | Out-File -Encoding unicode ($odir + "\utf16lewb.csv")
PS > Get-Content .\sjis.csv
1,ABC
2,abc
3,あいう
PS > Get-Content .\utf8nb.csv -Encoding utf8
1,ABC
2,abc
3,あいう

Rを起動して、read.table関数で読み込んでみる。現在の環境下における文字コードは特にオプションを指定しなくても読み込むことができる。

> Sys.getlocale()
[1] "LC_COLLATE=Japanese_Japan.utf8;LC_CTYPE=Japanese_Japan.utf8;LC_MONETARY=Japanese_Japan.utf8;LC_NUMERIC=C;LC_TIME=Japanese_Japan.utf8"
> read.table("sjis.csv", sep = ",")
V1 V2
1 no name
2 1 ABC
3 2 abc
4 3 \x82\xa0\x82\xa2\x82\xa4
> read.table("utf8nb.csv", sep = ",")
V1 V2
1 no name
2 1 ABC
3 2 abc
4 3 あいう

fileEncodingオプションにそれぞれ文字コードを指定する。UTF-16の場合、「UTF-16」を指定すればバイトオーダーマークからエンディアンを推定して読み込む。エンディアンを指定することもできる。最後の例のとおりに、エンディアンの指定を間違えると、指定のとおりに読み込もうとして読み込みに失敗する。

> read.table("sjis.csv", sep = ",", fileEncoding = "SJIS")
V1 V2
1 1 ABC
2 2 abc
3 3 あいう
> read.table("utf8nb.csv", sep = ",", fileEncoding = "UTF-8")
V1 V2
1 1 ABC
2 2 abc
3 3 あいう
> read.table("utf8wb.csv", sep = ",", fileEncoding = "UTF-8")
V1 V2
1 1 ABC
2 2 abc
3 3 あいう
> read.table("utf16bewb.csv", sep = ",", fileEncoding = "UTF16")
V1 V2
1 1 ABC
2 2 abc
3 3 あいう
> read.table("utf16lewb.csv", sep = ",", fileEncoding = "UTF16")
V1 V2
1 1 ABC
2 2 abc
3 3 あいう
> read.table("utf16bewb.csv", sep = ",", fileEncoding = "UTF-16BE")
V1 V2
1 1 ABC
2 2 abc
3 3 あいう
> read.table("utf16lewb.csv", sep = ",", fileEncoding = "UTF-16LE")
V1 V2
1 1 ABC
2 2 abc
3 3 あいう
> read.table("utf16lewb.csv", sep = ",", fileEncoding = "UTF-16BE")
V1
1 \ufffe\u3100Ⰰ䄀䈀䌀ഀ\u0a00㈀Ⰰ愀戀挀ഀ\u0a00㌀Ⰰ䈰䐰䘰ഀ\u0a00
警告メッセージ:
read.table("utf16lewb.csv", sep = ",", fileEncoding = "UTF-16BE") で:
incomplete final line found by readTableHeader on 'utf16lewb.csv'

2023年8月22日 (火)

[R]サイズが非常に大きなテキストファイルを簡単に作成する

動作確認でサイズが非常に大きなテキストファイルが必要なときがあるが、文字型ベクトルを使うと簡単に作成することができる。

以下は、Windows環境でサイズが5億バイト(≒476.8MB)のファイルを、一つのコマンドで作成している。10バイトの文字列("AB12あい"の8バイトと改行コードCR+LFの2バイト)を5,000万個作り、それをテキストファイルに出力している。文字コードはシフトJIS、改行コードはCR+LFとしているため、サイズが単純に10バイト×5,000万=5億バイトとなっている。作成には10分弱要している。

> cat(rep("AB12あい", 5 * 10 ^ 7), file = "temp.txt", sep = "\n")
> dir(".", "temp\\.txt")
[1] "temp.txt"
> file.size("temp.txt")
[1] 5e+08

2023年8月14日 (月)

[R]文字列型ベクトルを簡単にテキストファイルに出力する

cat関数を使う。sepオプションには文字型ベクトルの各要素の間に挟む文字列を指定する。以下の例では、最後のコマンドを実行後にはプロンプトは改行されないで表示される。

> lns <- c("ABC", "123", "あい")
> cat(lns, file = "temp.txt", sep = "\n")
> shell("type temp.txt")
ABC
123
あい
> cat(lns, file = "temp.txt", sep = ",")
> shell("type temp.txt")
ABC,123,あい

2023年7月14日 (金)

[R]警告メッセージ「line ○ appears to contain embedded nulls」

read.tableやread.csv関数を使用してこのメッセージが表示されたときは、読み込んだファイルにヌル(0x00、NULL)が含まれており、そのために正しく読み込めていない可能性が高い。skipNulオプションをTRUEにすると、ヌルをスキップして読み込むようになり、このメッセージが表示されなくなる。

> dtf <- read.csv("○○○", header = TRUE)
警告メッセージ:
read.table(file = file, header = header, sep = sep, quote = quote, で:
line ○ appears to contain embedded nulls
> dtf <- read.csv("○○○", header = TRUE, skipNul = TRUE)
>
無料ブログはココログ

■■

■■■