イレギュラーデータへの対処
Text Update: 11/10, 2018 (JST)

前回は Redmine カスタムフィールド を展開する方法を紹介しましたが、実際のデータには揺れがありイレギュラーとも言えるデータが混ざっています。このイレギュラーデータに対して前回の処理をそのまま適用するとSTOPエラーとなり処理が完了できません。そこで、イレギュラーデータにも対応できるカスタムフィールドの展開処理を説明します。

Packages and Datasets

本ページではR version 3.4.4 (2018-03-15)の標準パッケージ以外に以下の追加パッケージを用いています。
 

Package Version Description
redmineR 0.2.0 R Client for Redmine API
tidyverse 1.2.1 Easily Install and Load the ‘Tidyverse’

 
また、本ページでは以下のデータセットを用いています。
 

Dataset Package Version Description
issues N/A N/A Redmine Issues

 

イレギュラーを考慮した展開処理

カスタムフィールドは カスタムフィールドを展開する で説明した方法で展開可能ですが、実際のデータには以下のようにAffected versionvalueが存在しないというようなイレギュラーなデータが存在しています。
 

issues %>%
  dplyr::filter(id <= 16455 & id >= 16446) %>%
  dplyr::select(id, custom_fields) %>%
  tidyr::unnest() %>% 
  tidyr::drop_na() %>% knitr::kable()
id custom_fields
2 16451 list(id = 2, name = “Resolution”, value = “Invalid”)
3 16451 list(id = 4, name = “Affected version”)
4 16450 list(id = 2, name = “Resolution”, value = “”)
5 16449 list(id = 2, name = “Resolution”, value = “”)
6 16449 list(id = 4, name = “Affected version”, value = “75”)
7 16447 list(id = 2, name = “Resolution”, value = “Invalid”)
8 16447 list(id = 4, name = “Affected version”)

 
このようなデータ項目が存在しないイレギュラーなデータに対して参照を行うとSTOPエラーを起こしてしまい処理が完了できません。
 

issues %>%
  dplyr::filter(id <= 16455 & id >= 16446) %>%
  dplyr::select(id, custom_fields) %>%
  tidyr::unnest() %>%
  tidyr::drop_na() %>% 
  dplyr::mutate(name = purrr::map_chr(custom_fields, 'name'),
                value = purrr::map_chr(custom_fields, 'value'))
## Error in mutate_impl(.data, dots): Evaluation error: Result 2 is not a length 1 atomic vector.

 

そこで、イレギュラーなデータの存在を想定して別の方法でリスト型変数をデータフレーム型変数に展開する必要があります。
 

イレギュラーデータの確認

イレギュラーデータを再度確認しておきます。issue_id1645116447value項がないことが分かります。
 

sample_issues <- issues %>%
  dplyr::filter(id <= 16455 & id >= 16446) %>%
  dplyr::select(id, custom_fields) %>%
  tidyr::unnest() %>%
  tidyr::drop_na() %>%
  dplyr::rename(issue_id = id)

sample_issues %>% knitr::kable()
issue_id custom_fields
2 16451 list(id = 2, name = “Resolution”, value = “Invalid”)
3 16451 list(id = 4, name = “Affected version”)
4 16450 list(id = 2, name = “Resolution”, value = “”)
5 16449 list(id = 2, name = “Resolution”, value = “”)
6 16449 list(id = 4, name = “Affected version”, value = “75”)
7 16447 list(id = 2, name = “Resolution”, value = “Invalid”)
8 16447 list(id = 4, name = “Affected version”)

 

リスト型変数を結合して展開する

リスト型変数内にはチケット番号を識別するための識別子を持っていません(リスト型変数の最初のidは、カスタムフィールドの識別子です)。
 

sample_issues %>% 
  dplyr::select(custom_fields) %>% knitr::kable()
custom_fields
2 list(id = 2, name = “Resolution”, value = “Invalid”)
3 list(id = 4, name = “Affected version”)
4 list(id = 2, name = “Resolution”, value = “”)
5 list(id = 2, name = “Resolution”, value = “”)
6 list(id = 4, name = “Affected version”, value = “75”)
7 list(id = 2, name = “Resolution”, value = “Invalid”)
8 list(id = 4, name = “Affected version”)

 
そこで、リスト型変数内にチケット番号の識別子(ここではissue_idと名前を変えてあります)を入れ込んで識別できるようにしておきます。リスト型変数はc関数を用いて単純に結合するだけでリスト内の変数を追加できます。
 

c(issue_id = sample_issues$issue_id[1], sample_issues$custom_fields[[1]])
## $issue_id
## [1] 16451
## 
## $id
## [1] 2
## 
## $name
## [1] "Resolution"
## 
## $value
## [1] "Invalid"

 

この結合処理を各データに対して反復して適用するためにpurrrパッケージを利用します。
 

purrr::map2_df(sample_issues$issue_id, sample_issues$custom_fields,
               .f = function(.x, .y) {c(issue_id = .x, .y)}) %>% knitr::kable()
issue_id id name value
16451 2 Resolution Invalid
16451 4 Affected version NA
16450 2 Resolution
16449 2 Resolution
16449 4 Affected version 75
16447 2 Resolution Invalid
16447 4 Affected version NA

 
purrr::map2関数群は、purrr::map関数群を拡張した関数群で、二つの引数(.x, .y)に対して.fで指定した処理(関数や演算子など)を適用する関数です。  

これでチケット番号issue_id(元の識別名はid)に紐付いたカスタムフィールド情報を取得することができました。あとは、以前に学んだtidyr::spread関数を用いてカスタムフィールド(name)が変数になるように展開しておくとチケットデータと結合するのに便利です。
 

purrr::map2_df(sample_issues$issue_id, sample_issues$custom_fields,
               .f = function(.x, .y) {c(issue_id = .x, .y)}) %>%
  dplyr::select(-id) %>%
  tidyr::spread(key = name, value = value) %>%
  dplyr::rename(id = issue_id) %>% knitr::kable()
id Affected version Resolution
16447 NA Invalid
16449 75
16450 NA
16451 NA Invalid

 

チケットデータとの結合

カスタムフィールドが展開できましたので、あとは、チケットデータに対して識別子であるチケット番号(id)を元に結合処理を行います。

cf_list <- issues %>%
  dplyr::select(id, custom_fields) %>%
  tidyr::unnest() %>%
  tidyr::drop_na()

df_cf <- purrr::map2_df(cf_list$id, cf_list$custom_fields,
                        .f = function(.x, .y) {c(iid = .x, .y)}) %>%
  dplyr::select(-id) %>%
  tidyr::spread(key = name, value = value) %>%
  dplyr::rename(id = iid) %>%
  dplyr::arrange(dplyr::desc(id))

issues %>%
  dplyr::select(id, subject) %>%
  dplyr::left_join(df_cf, by = "id") %>% 
  dplyr::mutate_if(is.character, dplyr::na_if, "") %>% 
  head(10) %>% knitr::kable()
id subject Affected version Resolution
29855 add_working_days returns wrong date 138 NA
29853 Default plugin value is not 141 NA
29852 Cannot use Wiki link with a different name in the table of markdown NA Duplicate
29849 Display order of Redmine parent/child tickets are not correct 122 NA
29848 Query to determine role ids is incorrect 141 NA
29840 Cant update status_id from existing tickets 134 Invalid
29838 time logging via commit message does not work when the configured activity has been overridden on the project level NA NA
29830 Issue CSV export options: checkboxes have no effect for columns activated in query NA NA
29826 Can’t use back_url in Documents 141 NA
29824 Add user initials as a Gravatar icon fallback NA NA

 
ここでは表示の都合上、チケット番号、タイトル、カスタムフィールドのみに絞ってありますが、この結合処理により全てのリスト型変数に対する展開処理が終わったことになります。以降はお好きに処理してください。