前回は 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 version
にvalue
が存在しないというようなイレギュラーなデータが存在しています。
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_id
が16451
と16447
にvalue
項がないことが分かります。
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 |
ここでは表示の都合上、チケット番号、タイトル、カスタムフィールドのみに絞ってありますが、この結合処理により全てのリスト型変数に対する展開処理が終わったことになります。以降はお好きに処理してください。