オープン・クローズチャートを描く(その2)
Text Update: 11/24, 2018 (JST)

前回は基本となるオープン・クローズチャートを描きましたが、オープンしているチケットの詳細(優先度、カテゴリなど)が同時に表示されると更に分かりやすいオープン・クローズチャートになります。

Packages and Datasets

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

Package Version Description
forcats 0.3.0 Tools for Working with Categorical Variables (Factors)
ggplot2 3.1.0 Create Elegant Data Visualisations Using the Grammar of Graphics
knitr 1.20 A General-Purpose Package for Dynamic Report Generation in R
lubridate 1.7.4 Make Dealing with Dates a Little Easier
psych 1.8.10 Procedures for Psychological, Psychometric, and Personality Research
tidyverse 1.2.1 Easily Install and Load the ‘Tidyverse’

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

Dataset Package Version Description
issues NA NA Redmine.org issues

 

描画用データの作成

 

データの確認

オープン・クローズチャートを描くために Redmien公式 から REST API を用いて取得した以下のデータを用います。
 

id tracker status priority subject created_on closed_on
1 29767 Patch New Normal Traditional Chinese translation (to r17594) 2018-10-15 2018-06-26
2 29764 Feature New Normal new filed ‘Related Issues Notes’ for pair of related issues 2018-10-13 NA
3 29763 Feature Closed Normal Better linux distribution to install redmine 2018-10-13 2018-10-14
NA NA NA NA NA NA
555 27877 Feature Needs feedback Normal Optimze history navigation 2018-01-02 NA
556 27876 Feature Closed Normal add project id to robots.txt 2018-01-02 2018-01-31
557 27875 Feature New Normal SQL custom query 2018-01-02 NA

 

プロジェクト期間の設定

オープン・クローズチャートを描くためには横軸になるプロジェクト期間を設定しなければなりません。今回は2018年にオープンしたチケットデータを取得していますのでプロジェクト期間を以下とします。
 

(range <- data.frame(start = lubridate::as_date("2018/1/1"),
                     end = lubridate::as_date("2018/12/31"))) %>%
  knitr::kable()                            # 表示のための処理です
start end
2018-01-01 2018-12-31

 
上記の期間を元に日次のデータを作成しておきます。このデータがグラフの横軸となります。
 

(cal <- data.frame(date = with(range, seq(start, end, 1)))) %>% 
  head_tail()                               # 以下は表示のための処理です
date
1 2018-01-01
2 2018-01-02
3 2018-01-03
NA
363 2018-12-29
364 2018-12-30
365 2018-12-31

 

チケット数のカウント

横軸となるプロジェクト期間のデータが作成できましたので、次は縦軸となるチケットのオープン数とクローズ数をカウントし、そこから累積数を求めます。
 

オープン数

チケットのオープン日はチケット作成日(created_on)になりますので、作成日で集計するのは前回と同じですが、今回は更に優先度やカテゴリといった情報も含めますので、オープンしているチケット(クローズしていないチケット)のみを集計対象とします
 

(open <- issues %>% 
  dplyr::filter(status != "Closed") %>%     # クローズしていないチケットのみを取り出します
  tidyr::drop_na(created_on) %>%            # 念の為に作成日がNAのチケットを取り除きます
  dplyr::count(created_on, priority)) %>%   # 作成日と優先度で集計します 
  head_tail()                               # 表示のための処理です
created_on priority n
2018-01-02 Normal 2
2018-01-03 Normal 1
2018-01-04 Normal 1
NA NA
2018-10-11 Normal 3
2018-10-13 Normal 1
2018-10-15 Normal 1

 

クローズ数

同様にクローズ数はチケット終了日(closed_om)で集計を行います。ただし、Redmineのチケット・ステータスにはReopenがありますので、ステータスがClosedのチケットのみを集計対象にします。クローズチケットは既に終了したチケットですので優先度やカテゴリなどの内訳は見ないこととします。
 

(close <- issues %>% 
  dplyr::filter(status == "Closed") %>%     # クローズしたチケットのみを取り出します
  tidyr::drop_na(closed_on) %>%             # 念の為に終了日がNAのチケットを取り除きます
  dplyr::count(closed_on)) %>%              # 終了日で集計します
  head_tail()                               # 表示のための処理です
closed_on n
2018-01-03 3
2018-01-05 2
2018-01-07 2
NA
2018-10-11 1
2018-10-14 2
2018-10-15 2

 

プロジェクト期間との結合

ただし、クローズの最終日以降はこれからの進捗期間とみなし集計対象外とします(なので値は欠損値とします)。また、後で累積を計算しますので進捗期間内の欠損値はチケットなしとしてゼロにしておきます。
 

(oc_df <- cal %>% 
                                            # Openチケット数を結合しMessy Dataに変換します
  dplyr::left_join(., open, by = c("date" = "created_on")) %>% 
  tidyr::spread(key = priority, value = n) %>% 
                                            # Closeチケットの数を結合します
  dplyr::left_join(., close, by = c("date" = "closed_on")) %>% 
  dplyr::rename(close = n) %>% 
                                            # NAを一括して変換するためにTidy Dataに変換します
  tidyr::gather(key = "key", value = "n", -date) %>% 
  dplyr::mutate(n = dplyr::if_else(date <= dplyr::last(close$closed_on),
                                   dplyr::if_else(!is.na(n), n, 0L), NA_integer_)) %>% 
  tidyr::spread(key = key, value = n) %>%   # Messy Dataに変換します
  dplyr::rename(`No Status` = `<NA>`)) %>%  # 表示名を変更します
  head_tail()                               # 表示のための処理です
date No.Status High Low Normal Urgent close
1 2018-01-01 0 0 0 0 0 0
2 2018-01-02 0 0 0 2 0 0
3 2018-01-03 0 0 0 1 0 3
NA
363 2018-12-29 NA NA NA NA NA NA
364 2018-12-30 NA NA NA NA NA NA
365 2018-12-31 NA NA NA NA NA NA

 

累積数の算出

これで、日ごとのオープン数とクローズ数が求められましたので、オープン・クローズチャートを描くために必要な累積オープン数と累積クローズ数を求めます。

(cum_df <- oc_df %>% 
  dplyr::mutate_if(is.integer, cumsum) ) %>%  # 一括して累積数を求めます 
  head_tail()                                 # 表示のための処理です
date No.Status High Low Normal Urgent close
1 2018-01-01 0 0 0 0 0 0
2 2018-01-02 0 0 0 2 0 0
3 2018-01-03 0 0 0 3 0 3
NA
363 2018-12-29 NA NA NA NA NA NA
364 2018-12-30 NA NA NA NA NA NA
365 2018-12-31 NA NA NA NA NA NA

このままプロットしても構いませんが、ggplot2パッケージを利用するのであれば、Tidy Dataに変換しておくことをおすゝめします。Tidy Dataにすることでグルーピング操作や因子水準のを並べ変え操作などが楽になります。
 

cum_df %>% 
                                            # Tidy Dataに変換して因子水準を並べ替えます
  tidyr::gather(key = "status", value = n, -date) %>% 
  dplyr::mutate(status = forcats::fct_relevel(status, "Urgent", "High",
                                              "Normal", "Low", "No Status")) %>% 
                                            # 並べ替えた因子水準を利用してグラフを描きます
  ggplot2::ggplot(ggplot2::aes(x = date, y = n, fill = status)) +
    ggplot2::geom_area() +                  # 積み上げグラフとして描画します
    ggplot2::labs(x = "", y = "累積チケット数",
                  title = "オープンチケットのみ優先度で層別したオープンクローズチャート") +
    ggplot2::scale_fill_brewer(palette = "Spectral")

 
今回は、取得した全チケット(全てのトラッカー)のオープンチケットのみを優先度で分類してオープン・クローズチャートを描いてみました。

 
Enjoy!  


CC BY-NC-SA 4.0 , Sampo Suzuki