備忘ログ

チラシの裏的備忘録&メモ

Rで縦方向に連結されたcsvファイルを分割したい

chemstat.hatenablog.com

この記事をたまたまみてRでやってみようと思った。

こういう仕様のファイルは見たことがなかったが、任意の文字(値)で挟まれた範囲の行でデータフレームを分割したいという欲求はあり得るかと思った。

とりあえず今回の仕様について考えてみる。

データはもとのブログの記事からダウンロードできる。

ちなみに次のようにするとデータを再現できる。

df <- tibble::tibble(X1 = rep(c("Name", "Time", 1:25), 4),
                     X2 = sapply(1:4, \(x) c(letters[x], "Date", seq(2, 50, 2))) |> 
                       as.vector())

データを読み込むところから書いていく。

# csvファイルの読み書き用にread_csvとwrite_csvを使うために
# {readr}を読み込んでおく
library(readr)
# データの中に含まれている1行目のNameを鍵にするために値として読み込みたいので
# 1行目が列名として読み込まれないようにcol_namesをFALSEとしておく
df <- read_csv("./Measurement.csv", col_names = FALSE, show_col_types = FALSE)
# Name(データフレームの1行目1列目の値)が含まれる行番号をwhichで調べ値を持っておく
start_col <- which(df[1] == df[[1,1]])
# 分割したい範囲の最後の行番号を値として持っておく
# start_colの2つ目の値からのベクトルに1引いたものと、もとのデータフレームの最後の行番号(データフレームの行数)になる
end_col <- c(start_col[-1] - 1, nrow(df))

これで必要な値は作れたのであとは切り出していく。

まずはforループで書いてみる。csvファイルに書き出すというのも次にのせるが、Rで直接このあといじるならリストに各データフレームを入れておくほうが扱いやすそうだと思ったのでリストに格納する方法でやってみる。

# 分割したデータフレームを入れるリストを作っておく
df_split <- list()
# start_colの値の数だけループを回す
for(i in 1:length(start_col)){
  # リストのi番目に分割したデータフレームを入れる
  df_split[[i]] <- df[start_col[i]:end_col[i], ] 
  # 名前をつけておく
  names(df_split)[i] <- df[[start_col[i], 2]]
}

こうするとdf_splitのリストの中に分割された4つのデータフレームがあるので、名前もつけたのでほしいデータフレームを取り出せる。

# Nameがbのデータフレームを取り出すなら次のように取り出せる
# もちろん番号でも取り出せる
df_split["b"]
## $b
## # A tibble: 28 × 2
##    X1    X2   
##    <chr> <chr>
##  1 Name  b    
##  2 Time  Date 
##  3 1     2    
##  4 2     4    
##  5 3     6    
##  6 4     8    
##  7 5     10   
##  8 6     12   
##  9 7     14   
## 10 8     16   
## # … with 18 more rows

データフレームをcsvファイルに書き出すのはforループの場合は次のようにするとできる。

for(i in 1:length(start_col)){
write_csv(df[start_col[i]:end_col[i], ],
# ファイル名をMeasurement_名前のアルファベットにしておく
  file = paste0("Measurement_", df[[start_col[i], 2]], ".csv"),
# 行名はread_csvでつけられたものなのでcsvファイルに書き込まないようにする
  col_names = FALSE)
}

次に{purrr}でやってみる。ますはリスト形式でもつ方法を考える。map2をつかって、start_colend_colの値をつかって分割していく。

library(purrr)
df_split <- map2(start_col, end_col, \(x, y) df[x:y, ])

こうすると分割できる。次にデータフレームをcsvファイルに書き出す方法を考える。map2のままでもいいが、標準出力に切り出したデータフレームが出力されるのでwalk2を使う。

write_csvforループのときと変わらない。

walk2(start_col, end_col, \(x, y) write_csv(df[x:y, ], 
                                            file = paste0("Measurement_", df[[x, 2]], ".csv"),
                                            col_names = FALSE))

これで書き出せる。