【tidyxl】Rを使ってExcelから背景色のあるセルだけを抽出する

# 対象
+ エクセルが(宗教上)嫌いな人
+ なんでもRで片付けたい人

# はじめに
まず前提として、以下のようなエクセルファイルがあったとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269013/6c4de723-32cd-bf3b-e543-93fcfd4c8399.png)
ちなみにこのデータは[疑似個人情報データ生成サービス](https://hogehoge.tk/personal/)というWebサイトで作成しました。
この中で、色で塗りつぶされているセルだけを抽出して、データの解析を行いたいとします。**Excel職人**の方は「んなもんフィルタ掛けて「色で並べ替え」をクリックすればできるやん」と言うかもしれません。確かに言った通りのことをすればできるのですが、あのリボンの中からフィルタの項目探すの結構大変です（私だけ？）。さらに言えばシートの数が多かったり複数のエクセルでこれをいちいち手でやるのはメンドクサイ。マクロ組むなんてもっとメンドクサイ。
そんな方に朗報。**この操作、Rでもできます。**

# tidyxl
エクセルシートを読み込むためのパッケージとして、{tidyxl}というものがあります。「{tidyxl}？ {readxl}ちゃうんか？」と思うかもしれませんが、別物です。
インストールは例のごとく`install.packages("tidyxl")`。

{tidyxl}でエクセルファイルを読み込む関数に**xlsx_cells()**があります。{readxl}における`read_excel()`ですね。
この挙動を見てみましょう。

```{xlsx_cells.R}
library(tidyxl)
tidyxl::xlsx_cells("Sample_data.xlsx")

# A tibble: 1,206 x 21
   sheet  address   row   col is_blank data_type error logical numeric date   character       
   <chr>  <chr>   <int> <int> <lgl>    <chr>     <chr> <lgl>     <dbl> <dttm> <chr>           
 1 Sheet1 A1          1     1 FALSE    character NA    NA           NA NA     連番            
 2 Sheet1 B1          1     2 FALSE    character NA    NA           NA NA     氏名            
 3 Sheet1 C1          1     3 FALSE    character NA    NA           NA NA     氏名（カタカナ）
 4 Sheet1 D1          1     4 FALSE    character NA    NA           NA NA     性別            
 5 Sheet1 E1          1     5 FALSE    character NA    NA           NA NA     電話番号        
 6 Sheet1 F1          1     6 FALSE    character NA    NA           NA NA     生年月日        
 7 Sheet1 A2          2     1 FALSE    character NA    NA           NA NA     008             
 8 Sheet1 B2          2     2 FALSE    character NA    NA           NA NA     野原鉄太郎      
 9 Sheet1 C2          2     3 FALSE    character NA    NA           NA NA     ノハラテツタロウ
10 Sheet1 D2          2     4 FALSE    character NA    NA           NA NA     男              
# ... with 1,196 more rows, and 10 more variables: character_formatted <list>, formula <chr>, is_array <lgl>,
#   formula_ref <chr>, formula_group <int>, comment <chr>, height <dbl>, width <dbl>, style_format <chr>,
#   local_format_id <int>
```

少し様子が違うことに気づいたでしょうか？そうです、この関数は**各セルの情報(シート番号、アドレス、書いてある内容 etc...)を一つの行で格納している**のです。
各セルのフォーマットについてはidが振られており、`変数$local_format_id`でアクセスすることができます。

```{local_format_id.R}
cells <- tidyxl::xlsx_cells("Sample_data.xlsx")
cells$local_format_id[1:10] #長いので一部を抜粋
> cells$local_format_id[1:10]
 [1] 4 4 5 4 4 4 9 9 9 9
```
１番目からA1, B1, C1,...のセルの書式について数字が振られています。A1 ～ F1までの書式がボールド体である一方、A2から通常の書式になっているので番号が大きく変わっています。しかしこれだけでは、「どのセルが塗りつぶされたセルか」がわかりません。そこで、同じパッケージ内にある関数**xlsx_formats()**を使います。

[公式](https://rdrr.io/github/nacnudus/tidyxl/man/tidy_xlsx.html)曰く、

>
Cell formatting is returned by xlsx_formats(). ~(中略)~
To look up the local formatting of a given cell, take the cell's `local_format_id` value (`my_cells$Sheet1[1, "local_format_id"]`), and use it as an index into the format structure. E.g. to look up the font size, `my_formats$local$font$size[local_format_id]`. To see all available formats, type `str(my_formats$local)`.
 
(DeepL訳：セルの書式設定はxlsx_formats()で返されます。これらは、xlsx_formats()の`$style`サブリストと`$local`サブリストで返され、構造は同じです。あるセルのローカルな書式を調べるには、そのセルの`local_format_id` 値 (`my_cells$Sheet1[1, "local_format_id"]`) を取り、それを書式構造のインデックスとして使用します。例えば、フォントサイズを調べるには`my_formats$local$font$size[local_format_id]`と入力します。利用可能なすべてのフォーマットを見るには、`str(my_formats$local)`と入力します。)

とあります。ちょっと何言ってるかわからないと思うので噛み砕いて説明します。

まず`xlsx_formats()`を使うと、各セルの全ての書式情報(フォントのサイズや色、罫線の有無、背景色 etc...)。が格納されたオブジェクト(リスト)が生成されます。先程、`cells$local_format_id`で得られた番号が割り当てられるということを話しましたが、実際には`xlsx_formats()`によって割り当てる番号を決定していることになります。
セルの背景色については`変数$local$fill$patternFill$fgColor$rgb[変数$local_format_id]`でアクセスすることができます。
つまり、

```{result.R}
cells <- tidyxl::xlsx_cells("Sample_data.xlsx")
formats <- xlsx_formats("Sample_data.xlsx")

formats$local$fill$patternFill$fgColor$rgb[cells$local_format_id][1:50]

> formats$local$fill$patternFill$fgColor$rgb[cells$local_format_id][1:50]
 [1] NA         NA         NA         NA         NA         NA        
 [7] NA         NA         NA         NA         NA         NA        
[13] NA         NA         NA         NA         NA         NA        
[19] NA         NA         NA         NA         NA         NA        
[25] NA         NA         NA         NA         NA         NA        
[31] NA         NA         NA         NA         NA         NA        
[37] NA         NA         NA         NA         NA         NA        
[43] NA         NA         NA         NA         NA         NA        
[49] "FF70AD47" "FF70AD47"
```  

これで背景色の情報を得ることができました！見てみると多くはNA(背景色なし)ですが、
`FF70AD47`というデータがあります。これが今回探している背景色のカラーコードです。

ここまで来れば後は簡単！
`local_format_id`の中で"FF70AD47"というカラーコードになっている行をフィルターすれば背景色のあるセルのみを抽出できます。

```{filter.R}
  cells %>% 
  filter(
    local_format_id %in% which(formats$local$fill$patternFill$fgColor$rgb == "FF70AD47")
    ) 

# A tibble: 210 x 21
   sheet  address   row   col is_blank data_type error logical numeric
   <chr>  <chr>   <int> <int> <lgl>    <chr>     <chr> <lgl>     <dbl>
 1 Sheet1 A9          9     1 FALSE    character NA    NA           NA
 2 Sheet1 B9          9     2 FALSE    character NA    NA           NA
 3 Sheet1 C9          9     3 FALSE    character NA    NA           NA
 4 Sheet1 D9          9     4 FALSE    character NA    NA           NA
 5 Sheet1 E9          9     5 FALSE    character NA    NA           NA
 6 Sheet1 F9          9     6 FALSE    character NA    NA           NA
 7 Sheet1 A14        14     1 FALSE    character NA    NA           NA
 8 Sheet1 B14        14     2 FALSE    character NA    NA           NA
 9 Sheet1 C14        14     3 FALSE    character NA    NA           NA
10 Sheet1 D14        14     4 FALSE    character NA    NA           NA
# ... with 200 more rows, and 12 more variables: date <dttm>,
#   character <chr>, character_formatted <list>, formula <chr>,
#   is_array <lgl>, formula_ref <chr>, formula_group <int>,
#   comment <chr>, height <dbl>, width <dbl>, style_format <chr>,
#   local_format_id <int>
```

A9からのtibbleになっていることから、正しく背景色のあるセルだけを選択できています。解析の際には、これをtidy dataに直せば良いのでRのみでセル抽出→データ解析ができるようになります。また、「背景色がある」という条件だけでいいのであれば、filter()の条件式を`!= "NA"`と記述することでも同様の結果が得られます。
（`pivot_wider()`や`select()`については多くの記事があるので割愛）

```{result.R}
pacman::p_load(
  tidyverse,
  readxl,
  here,
  tidyxl 
)

col_names_excel <- read_excel("Sample_data.xlsx") %>% colnames() # もともとの列名を保存

cells <- tidyxl::xlsx_cells("Sample_data.xlsx") # セルの情報を取得
formats <- xlsx_formats("Sample_data.xlsx") # セルの書式情報を取得

df <- 
  cells %>% 
  filter(
    local_format_id %in% which(formats$local$fill$patternFill$fgColor$rgb != "NA")
    ) %>%
  select(row, col, character) %>% 
  pivot_wider(
    names_from = col,
    values_from = character
  ) %>% select(!row) 

colnames(df) <- col_names_excel

> df
# A tibble: 35 x 6
   連番  氏名       `氏名（カタカナ）` 性別  電話番号   生年月日  
   <chr> <chr>      <chr>              <chr> <chr>      <chr>     
 1 008   野原鉄太郎 ノハラテツタロウ   男    0877500712 1973/10/10
 2 013   吉岡春奈   ヨシオカハルナ     女    0155191924 1994/09/27
 3 014   荻野飛鳥   オギノアスカ       女    0873632701 1989/03/05
 4 016   川島知治   カワシマトモハル   男    0441682525 1992/03/30
 5 018   関口希実   セキグチノゾミ     女    0234256551 1979/07/22
 6 022   猪俣遥華   イノマタハルカ     女    0192956110 1971/03/30
 7 024   三宅穂乃佳 ミヤケホノカ       女    0189808393 1971/12/07
 8 025   野呂千春   ノロチハル         女    0883007604 1969/01/31
 9 031   小森実     コモリミノル       男    0990693955 1970/09/21
10 037   持田麗奈   モチダレナ         女    0451228474 1996/06/15
# ... with 25 more rows
```

# おわりに
今回の記事では「色で塗りつぶされたセルを抽出する」ということにフォーカスをしました。しかし、この方法を応用することで「フォントがbold体になっているセル」や「コメントの入っているセル」の抽出も可能になります。
割とクセがありますが一度関数化してしまえば反復処理もできるので非常に良いパッケージと言えるのではないでしょうか。