CSVの代わりにParquetを使ってみよう

parquet
arrow
R
Python
Japanese
公開

2022年6月12日

本記事では,CSVの代替として有望かつビッグデータ分析にも適しているParquetを紹介します.

さて,データフレーム(Data Frames)は,データ分析において最も基本的なデータ構造の1つです.RのtibbledplyrPythonのpandasなどのデータフレーム操作のためのパッケージを使えば,これまでExcelなどの表計算ソフトで行っていたデータ分析をさらに効率的に行うことができます.

このようにデータ分析ツールが充実している一方で,データの保存にはExcelなどとの互換性が高いCSVが未だに広く使われています.しかし,CSVは,必ずしもデータ分析に適したファイル形式とは言えません.そこで,CSVの代替として使われることが多くなっているParquetをCSVと比較してみましょう.

サンプルデータの準備

CSVとParquetを比較するため,まずは,データ分析にありがちなサンプルデータを用意しましょう.今回は,tidyrパッケージで提供されているwho世界保健機関(WHO)結核データ)からサンプルデータをつくります.

近年,データ分析では,整然データ(tidy dataの概念が普及しています.tidy dataは,個々の変数が1つの列をなし,個々の観測(値)が1つの行をなすようなデータです.

それでは,whoは,tidy dataと言えるでしょうか?whoには,"new_sp_m014""newrel_f65" といったたくさんの列が存在しますが,これらには,1列ごとに,診断結果(spsel)・性別(mf)・年齢階級(01465)といった複数の変数が含まれています.そのため,who は,tidy dataでないといえます.そこで,こちらに従ってtidy dataであるwho_longerに変形します.

データ分析では,who よりtidy dataであるwho_longer のほうを分析が行いやすい一方で,行数はwho(約7,000行)よりwho_longer (約400,000行)のほうが約50倍多いことがわかります.そのため,tidy dataであるwho_longerのようなデータをテキストファイルであるCSVで保存すると容量が増大してしまいます.

このように,tidy dataはデータ分析に適している一方で,CSVのようなテキストファイルでの保存に適していないことがわかります.しかし,このようなデータ保存上の課題はParquetを使えば解決することができます.

ここで,tidy dataでないwho とtidy dataであるwho_longer を見比べてみましょう.

library(tidyverse)
Warning: package 'ggplot2' was built under R version 4.2.3
Warning: package 'tibble' was built under R version 4.2.3
Warning: package 'dplyr' was built under R version 4.2.3
library(fs)
コード
levels_gender <- c("f", "m")
levels_age <- c("014", "1524", "2534", "3544", "4554", "5564", "65")

who_longer <- who |> 
  pivot_longer(cols = new_sp_m014:newrel_f65,
               names_to = c("diagnosis", "gender", "age"), 
               names_pattern = "new_?(.*)_(.)(.*)",
               names_transform = list(gender = ~ .x |> 
                                        readr::parse_factor(levels = levels_gender),
                                      age = ~ .x |> 
                                        readr::parse_factor(levels = levels_age,
                                                            ordered = TRUE)),
               values_to = "count")
# データ整形前
print(who, n = 5)
# A tibble: 7,240 × 60
  country   iso2  iso3   year new_sp_m014 new_sp_m1524 new_sp_m2534 new_sp_m3544
  <chr>     <chr> <chr> <dbl>       <dbl>        <dbl>        <dbl>        <dbl>
1 Afghanis… AF    AFG    1980          NA           NA           NA           NA
2 Afghanis… AF    AFG    1981          NA           NA           NA           NA
3 Afghanis… AF    AFG    1982          NA           NA           NA           NA
4 Afghanis… AF    AFG    1983          NA           NA           NA           NA
5 Afghanis… AF    AFG    1984          NA           NA           NA           NA
# ℹ 7,235 more rows
# ℹ 52 more variables: new_sp_m4554 <dbl>, new_sp_m5564 <dbl>,
#   new_sp_m65 <dbl>, new_sp_f014 <dbl>, new_sp_f1524 <dbl>,
#   new_sp_f2534 <dbl>, new_sp_f3544 <dbl>, new_sp_f4554 <dbl>,
#   new_sp_f5564 <dbl>, new_sp_f65 <dbl>, new_sn_m014 <dbl>,
#   new_sn_m1524 <dbl>, new_sn_m2534 <dbl>, new_sn_m3544 <dbl>,
#   new_sn_m4554 <dbl>, new_sn_m5564 <dbl>, new_sn_m65 <dbl>, …
# データ整形後
print(who_longer, n = 5)
# A tibble: 405,440 × 8
  country     iso2  iso3   year diagnosis gender age   count
  <chr>       <chr> <chr> <dbl> <chr>     <fct>  <ord> <dbl>
1 Afghanistan AF    AFG    1980 sp        m      014      NA
2 Afghanistan AF    AFG    1980 sp        m      1524     NA
3 Afghanistan AF    AFG    1980 sp        m      2534     NA
4 Afghanistan AF    AFG    1980 sp        m      3544     NA
5 Afghanistan AF    AFG    1980 sp        m      4554     NA
# ℹ 405,435 more rows

CSV・Parquetの保存方法

Rでは,write_csv() でCSVを保存できます.同様に,arrowパッケージのwrite_parquet() でParquetを保存することができます.who_longerをCSVとParquetで保存してみましょう.

CSVとParquetでは,どちらも簡単にデータ保存ができることがわかります.

library(arrow)
Warning: package 'arrow' was built under R version 4.2.3
# CSVを保存
write_csv(who_longer, "who_longer.csv")

# Parquetを保存
write_parquet(who_longer, "who_longer.parquet")

Parquetのメリット・CSVとの比較

ここからは,保存したwho_longer のCSV・Parquetファイルを比較して,CSVに対するParquetのメリットを紹介していきます.

メリット1:CSVよりデータ容量が軽い

tidy dataは行数が多くなるため,CSVでの保存に適しておらず,Parquetを使ったほうがよいことを既に述べました.

実際に,who_longer のCSV・Parquetのデータ容量は,それぞれ,14.1 MBと154 KBとなり,ParquetはCSVの約1 %のデータ容量しかないことがわかります.

どのようなケースでもこのようなデータ容量の削減が見込めるわけではありませんが,Parquetは列指向でデータ圧縮を行うため,Rなどでよく用いられるtidy dataの保存に適したデータ形式であるといえます.

# CSV
file_size("who_longer.csv")
14.1M
# Parquet
file_size("who_longer.parquet")
156K
units::set_units(file_size("who_longer.parquet") / file_size("who_longer.csv")) |> 
  units::set_units(`%`)
1.080502 [%]

メリット2:CSVより読み込みが簡単

write_csv()write_parquet() でデータを書き込めるのと同様に,read_csv()read_parquet() でCSV・Parquetデータを読み込むことができます.

CSVはテキスト形式であるため,読み込み時にcol_typesで各列の型を指定する必要があります(デフォルトでは自動で型を推測).

一方,Parquetは,書き込み時に各列の型情報も保存されているため読み込み時に型を指定する必要がありません

# CSVの読み込み
read_csv("who_longer.csv",
         col_types = cols(.default = "c",
                          year = "i",
                          count = "i"))

# Parquetの読み込み
read_parquet("who_longer.parquet")

メリット3:CSVよりビッグデータの読み込み・集計に適している

CSVはビッグデータの保存に適しておらず,これまでは,ビッグデータの保存にはSQLを用いるなどの使い分けが必要でした.

Rでは,dplyr(dbplyr)・DBIなどのパッケージで簡単にSQLが使えますが,データベースへの接続・切断などが必要なSQLは,CSVと使い勝手が異なり,初学者にとってはハードルがあるかもしれません.

また,(ほとんどの?)SQLは行指向であるため,データの追加・更新・削除などに適していますが,データ分析に用いられるデータの保存・集計には列指向であるParquetのほうが適していると思われます.

CSVファイルを用いてビッグデータを集計する場合には,一度,全データをメモリに移す必要があります.そのため,データの読み込みでメモリが逼迫するおそれがあります.

Parquetでは,読み込み時にas_data_frame = FALSEとすることで,SQLと同様にメモリにデータを移すことなくデータのフィルタリング・集計などが可能です.

ここでは,日本の年・症例別の患者数を計算してみましょう.dplyrのfilter()group_by()summarise() などを使って効率的にクエリを作成することができます.最後にcollect() を行えばデータフレームを出力することができます.

read_parquet("who_longer.parquet",
             as_data_frame = FALSE) |> 
  filter(country == "Japan",
         !is.na(count)) |> 
  group_by(country, year, diagnosis) |> 
  summarise(count = sum(count),
            .groups = "drop") |> 
  collect()
# A tibble: 33 × 4
   country  year diagnosis count
   <chr>   <dbl> <chr>     <dbl>
 1 Japan    1995 sp        14367
 2 Japan    1996 sp        12867
 3 Japan    1997 sp        13571
 4 Japan    1998 sp        11935
 5 Japan    2001 sp        11408
 6 Japan    2002 sp        10807
 7 Japan    2003 sp        10843
 8 Japan    2004 sp        10471
 9 Japan    1999 sp        12909
10 Japan    2000 sp        11853
# ℹ 23 more rows

メリット4:複数のデータからなるデータセットを扱える

Parquetは列指向であるため,行指向であるSQLと違い,データの追加・更新・削除などに適していません.しかし,Parquetでは,複数のデータからなるデータセットの読み込みが簡単に行えるため,このようなデメリットを簡単に解決することができます.

ここでは,who_longerを年齢階級別に分割したParquetファイルを格納した"who_longer_byage" フォルダをデータセットのサンプルとして用いましょう.

open_dataset("who_longer_byage") とすることで,複数のParquetファイルを含むにもかかわらず,さきほどと同様のデータ集計を簡単に行うことができます

コード
dir_create("who_longer_byage")
who_longer |> 
  group_by(age) |> 
  group_walk(~ .x |> 
               write_parquet(str_glue("who_longer_byage/who_longer_{.y$age}.parquet")),
  .keep = TRUE)
open_dataset("who_longer_byage") |> 
  filter(country == "Japan",
         !is.na(count)) |> 
  group_by(country, year, diagnosis) |> 
  summarise(count = sum(count),
            .groups = "drop") |> 
  collect()
# A tibble: 33 × 4
   country  year diagnosis count
   <chr>   <dbl> <chr>     <dbl>
 1 Japan    1995 sp        14367
 2 Japan    1996 sp        12867
 3 Japan    1997 sp        13571
 4 Japan    1998 sp        11935
 5 Japan    2001 sp        11408
 6 Japan    2002 sp        10807
 7 Japan    2003 sp        10843
 8 Japan    2004 sp        10471
 9 Japan    2005 sp        10931
10 Japan    2006 sp        10159
# ℹ 23 more rows

メリット5:R・Python間でのデータのやり取りに適している

PythonのpandasパッケージはParquetの読み書きに対応しているため,Parquetは,R・Python間でのデータのやり取りにも適しています.

Rで作成した'who_longer.parquet' をpandasで読み込んでみましょう.

import pandas as pd

pd.read_parquet('who_longer.parquet')
            country iso2 iso3    year diagnosis gender   age   count
0       Afghanistan   AF  AFG  1980.0        sp      m   014     NaN
1       Afghanistan   AF  AFG  1980.0        sp      m  1524     NaN
2       Afghanistan   AF  AFG  1980.0        sp      m  2534     NaN
3       Afghanistan   AF  AFG  1980.0        sp      m  3544     NaN
4       Afghanistan   AF  AFG  1980.0        sp      m  4554     NaN
...             ...  ...  ...     ...       ...    ...   ...     ...
405435     Zimbabwe   ZW  ZWE  2013.0       rel      f  2534  4649.0
405436     Zimbabwe   ZW  ZWE  2013.0       rel      f  3544  3526.0
405437     Zimbabwe   ZW  ZWE  2013.0       rel      f  4554  1453.0
405438     Zimbabwe   ZW  ZWE  2013.0       rel      f  5564   811.0
405439     Zimbabwe   ZW  ZWE  2013.0       rel      f    65   725.0

[405440 rows x 8 columns]

まとめ

ここまで,R・Pythonで利用可能なParquetのメリットを紹介しました.Parquetは,近年,データ分析で普及しているtidy dataの保存・集計に適しています.

また,最近では,地理データを扱えるsfパッケージのデータをparquetとして保存できるsfarrowなども登場しています.

CSVの代わりにParquetを用いることでデータ分析がさらに簡単になることが期待されます.