からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

2つの日付間の経過日数(y年mか月とd日)を求めたい

はじめに

 素直なやり方ではできなかったのでむりくりなんとかする黒魔術シリーズです。もっといい方法があれば教えてください。

 この記事では、R言語で経過期間の日付計算を行います。

【目次】

2つの日付間の経過日数(y年mか月とd日)を求めたい

 ある日付からの経過期間(年・月・日数)を「何年何か月と何日」の形式で求めたい。総年数や総月数、総日数であればlubridateパッケージの関数で簡単に求められるが、前月の基準となる日にちからの経過日数を求めるのは(たぶん)ややこしいのでやってみる。簡単に求められるのであれば教えてほしい。

 利用するパッケージを読み込む。

# 利用パッケージ
library(tidyverse)
library(lubridate)

 この記事では基本的に、パッケージ名::関数名()の記法を使うので、パッケージを読み込む必要はない。ただし、作図コードについてはパッケージ名を省略するため、ggplot2を読み込む必要がある。
 また、magrittrパッケージのパイプ演算子%>%ではなく、ネイティブパイプ演算子|>を使う。%>%に置き換えても処理できるが、その場合はmagrittrを読み込む必要がある。

月ごとの経過日数

 まずは簡単な例として、毎月1日時点の経過期間を求める。

期間計算

 基準となる日と期間を設定する。

# 基準日を指定
date_from <- "2022-01-15" |> 
  lubridate::as_date()

# 期間の最終日を指定
date_max <- "2023-02-15" |> 
  lubridate::as_date()
date_from; date_max
## [1] "2022-01-15"
## [1] "2023-02-15"

 各期間の開始日をdate_from、期間の最終日(の月)をdate_maxとして"yyyy-mm-dd"などの形式の文字列で指定して、as_date()で日付型の値に変換する。
 date_fromを基準日、date_maxまでの日を経過日と呼ぶことにする。

 月ごとに経過期間を計算する。

# 基準日以降の月ごとに経過期間を計算
month_df <- tibble::tibble(
  # 1か月間隔の日付を作成
  date = seq(
    from = date_from |> 
      lubridate::rollforward(roll_to_first = TRUE), # 基準日の翌月の初日
    to = date_max |> 
      lubridate::floor_date(unit = "month"), # 期間の最終日の月の初日
    by = "month"
  )
) |> 
  dplyr::mutate(
    # 期間計算用の値を作成
    date_day = date |> 
      lubridate::day() |> 
      as.numeric(), # 経過日の日にち
    from_day = date_from |> 
      lubridate::day() |> 
      as.numeric(), # 基準日の日にち
    pre_last_day = date |> 
      lubridate::rollback() |> 
      lubridate::day() |> 
      as.numeric(), # 経過日の前月の末日
    # 経過期間を計算
    y = lubridate::interval(start = date_from, end = date) |> 
      lubridate::time_length(unit = "year") |> 
      floor(), # 経過年数
    m = lubridate::interval(start = date_from, end = date) |> 
      lubridate::time_length(unit = "month") |> 
      floor() %% 12, # 経過月数
    d = dplyr::case_when(
      from_day >= pre_last_day ~ 1, # 基準日の日にちが無い月の場合は「1にち」
      from_day == 1 ~ 0, # 基準日が月初の場合は「0にち」
      from_day <= pre_last_day ~ pre_last_day - from_day + 1 # 基準日の日にちが有る月の場合は「前月における基準日からの日数」
    ), # 経過日数
    days = lubridate::interval(start = date_from, end = date) |> 
      lubridate::time_length(unit = "day"), # 総経過日数
    ymd_label = paste0(y, "年", m, "か月", d, "日") # 経過期間ラベル
  )
month_df |> 
  dplyr::select(!date_day) # 資料用に間引き
## # A tibble: 13 × 8
##    date       from_day pre_last_day     y     m     d  days ymd_label    
##    <date>        <dbl>        <dbl> <dbl> <dbl> <dbl> <dbl> <chr>        
##  1 2022-02-01       15           31     0     0    17    17 0年0か月17日 
##  2 2022-03-01       15           28     0     1    14    45 0年1か月14日 
##  3 2022-04-01       15           31     0     2    17    76 0年2か月17日 
##  4 2022-05-01       15           30     0     3    16   106 0年3か月16日 
##  5 2022-06-01       15           31     0     4    17   137 0年4か月17日 
##  6 2022-07-01       15           30     0     5    16   167 0年5か月16日 
##  7 2022-08-01       15           31     0     6    17   198 0年6か月17日 
##  8 2022-09-01       15           31     0     7    17   229 0年7か月17日 
##  9 2022-10-01       15           30     0     8    16   259 0年8か月16日 
## 10 2022-11-01       15           31     0     9    17   290 0年9か月17日 
## 11 2022-12-01       15           30     0    10    16   320 0年10か月16日
## 12 2023-01-01       15           31     0    11    17   351 0年11か月17日
## 13 2023-02-01       15           31     1     0    17   382 1年0か月17日

 「date_fromの翌月の1日」から「date_maxの月の1日」まで1か月間隔の日付をseq(by = "month")で作成して、経過日列dateとしてデータフレームに格納する。

 条件式や計算用に、経過日の日にちをdate_day列、基準日の日にちをfrom_day列、経過日の前月末の日にちをpre_last_day列としておく。日にちの値はday()、前月の月末の日付はrollback()で得られる。

 基準日date_fromから各経過日dateまでの経過年数をinterval()time_length(unit = "year")で計算してy列とする。
 総経過月数をinterval()time_length(unit = "month")で計算して、1年の月数12で割った余りを経過月数m列とする。商余は%%で計算できる。
 経過日数は、case_when()を使って「基準日の日にち」によって場合分けして計算して、d列とする。

  • 「基準の日にち」が「前月に無い」(from_daypre_last_dayより大きい)または「前月末の日にち」(from_daypre_last_dayが同じ)の場合は、基準の日にちを月末とみなして、1にちである。
  • 「基準の日にち」が「経過日と同じ日にち(1日)」(from_day1)の場合は、0にちである。
  • 「基準の日にち」が「前月に有る(前月末の日にちより前の日にち)」(from_daypre_last_dayより小さい)場合は、前月の基準の日から月末までの日数(pre_last_day - from_day)の1にち後である。「基準の日にち」が「前月末の日にち」(from_daypre_last_dayが同じ)の場合は、こちらの式でも計算できる。

 総経過日数はinterval()time_length(unit = "day")で計算できる。
 経過期間列y, m, dを用いて、経過期間ラベルを作成してymd_label列とする。

日ごとの経過日数

 続いて、日ごとに経過期間を求める。

期間計算

 基準となる日と期間を設定して、日ごとに経過期間を計算する。

# 基準日を指定
date_from <- "2022-01-15" |> 
  lubridate::as_date()

# 期間の最終日を指定
date_max <- "2023-02-20" |> 
  lubridate::as_date()

# 基準日以降の日ごとに経過期間を計算
date_df <- tibble::tibble(
  # 1日間隔の日付を作成
  date = seq(from = date_from , to = date_max, by = "day")
) |> 
  dplyr::mutate(
    # 期間計算用の値を作成
    date_day = date |> 
      lubridate::day() |> 
      as.numeric(), # 経過日の日にち
    from_day = date_from |> 
      lubridate::day() |> 
      as.numeric(), # 基準日の日にち
    pre_last_day = date |> 
      lubridate::rollback() |> 
      lubridate::day() |> 
      as.numeric(), # 経過日の前月の末日
    # 経過期間を計算
    y = lubridate::interval(start = date_from, end = date) |> 
      lubridate::time_length(unit = "year") |> 
      floor(), # 経過年数
    m = lubridate::interval(start = date_from, end = date) |> 
      lubridate::time_length(unit = "month") |> 
      floor() %% 12, # 経過月数
    d = dplyr::case_when(
      (from_day > date_day & from_day <  pre_last_day) ~ pre_last_day - from_day + date_day, # 前月の途中から
      (from_day > date_day & from_day >= pre_last_day) ~ date_day, # 当月の頭から
      from_day <= date_day ~ date_day - from_day # 当月の途中から
    ), # 経過日数
    days = lubridate::interval(start = date_from, end = date) |> 
      lubridate::time_length(unit = "day"), # 総経過日数
    ymd_label = paste0(y, "年", m, "か月", d, "日") # 経過期間ラベル
  )
date_df |> 
  dplyr::select(!date_day) # 資料用に間引き
## # A tibble: 402 × 8
##    date       from_day pre_last_day     y     m     d  days ymd_label  
##    <date>        <dbl>        <dbl> <dbl> <dbl> <dbl> <dbl> <chr>      
##  1 2022-01-15       15           31     0     0     0     0 0年0か月0日
##  2 2022-01-16       15           31     0     0     1     1 0年0か月1日
##  3 2022-01-17       15           31     0     0     2     2 0年0か月2日
##  4 2022-01-18       15           31     0     0     3     3 0年0か月3日
##  5 2022-01-19       15           31     0     0     4     4 0年0か月4日
##  6 2022-01-20       15           31     0     0     5     5 0年0か月5日
##  7 2022-01-21       15           31     0     0     6     6 0年0か月6日
##  8 2022-01-22       15           31     0     0     7     7 0年0か月7日
##  9 2022-01-23       15           31     0     0     8     8 0年0か月8日
## 10 2022-01-24       15           31     0     0     9     9 0年0か月9日
## # … with 392 more rows

 基準日date_fromと期間の最終日date_maxを日付型の値で指定する。
 date_fromからdate_maxまでの1日間隔の日付をseq(by = "day")を作成して、経過日列dateとしてデータフレームに格納する。

 「月ごとの経過日数」のときと同様にして、経過日の日にち列date_day、基準日の日にち列from_day、経過日の前月末の日にち列pre_last_dayを作成しておく。
 また、基準日date_fromから各経過日dateまでの経過年数列y、経過月数列mを計算する。

 経過日数列dは、「基準日の日にち」と「経過日の日にち」または「経過日の前月末の日にち」との大小関係によって場合分けして計算する。

  • 「経過日の日にち」から「基準の日にち」まで(form_daydate_dayより大きい場合)の日数を、さらに2つの場合に応じて計算する。
    • 「基準の日にち」が「前月に有る(前月末の日にちより前の日にち)」(from_daypre_last_dayより小さい)場合は、「前月の基準の日から月末までの日数」(pre_last_day - from_day)と「当月の経過日数」(date_day)の和である。
    • 「基準の日にち」が「前月に無い」(from_daypre_last_dayより大きい)または「前月末の日にち」(from_daypre_last_dayが同じ)の場合は、基準の日を月末とみなして、「当月の経過日数」(date_day)である。
  • 「基準の日にち」から「経過日の日にち」まで(form_daydate_day以下の場合)の日数は、「当月の基準の日から経過日までの日数」(date_day - from_day)である。

 経過日数列y, m, dを用いて、経過日数ラベルを作成してymd_label列とする。

 以上で、経過期間を計算できた。続いて、計算結果を確認する。

ヒートマップで確認

 表形式では計算が合っているのか分かりにくいので、ヒートマップとラベルで確認する。

 ヒートマップの作図用のデータフレームを作成する。

# ヒートマップ用に経過期間データを整形
heatmap_df <- date_df |> 
  dplyr::select(date, d, ymd_label, day = date_day) |> 
  dplyr::mutate(
    year_month = date |> 
      format(format = "%Y-%m") |> 
      factor() # 年月ラベル
  )
heatmap_df
## # A tibble: 402 × 5
##    date           d ymd_label     day year_month
##    <date>     <dbl> <chr>       <dbl> <fct>     
##  1 2022-01-15     0 0年0か月0日    15 2022-01   
##  2 2022-01-16     1 0年0か月1日    16 2022-01   
##  3 2022-01-17     2 0年0か月2日    17 2022-01   
##  4 2022-01-18     3 0年0か月3日    18 2022-01   
##  5 2022-01-19     4 0年0か月4日    19 2022-01   
##  6 2022-01-20     5 0年0か月5日    20 2022-01   
##  7 2022-01-21     6 0年0か月6日    21 2022-01   
##  8 2022-01-22     7 0年0か月7日    22 2022-01   
##  9 2022-01-23     8 0年0か月8日    23 2022-01   
## 10 2022-01-24     9 0年0か月9日    24 2022-01   
## # … with 392 more rows

 この例では、各経過日を1つのセルにするためx軸を年月、y軸を日として、各経過日の前月の基準の日からの日数で濃淡を付けて可視化する。date列から年・月部分をformat()で文字列として取り出す。

 ヒートマップとラベルで経過期間を確認する。

# 経過期間をヒートマップで可視化
ggplot() + 
  geom_tile(data = heatmap_df, 
            mapping = aes(x = year_month, y = day, fill = d)) + # 日付セル
  geom_tile(mapping = aes(x = format(date_from, format = "%Y-%m"), y = lubridate::day(date_from)), 
            fill = "blue", color = "blue", alpha = 0.1) + # 基準日セル
  geom_text(data = heatmap_df, 
            mapping = aes(x = year_month, y = day, label = ymd_label)) + # 経過期間ラベル
  scale_y_reverse(breaks = 1:31) + # 日軸
  scale_fill_gradient(low = "white" , high = "#00A968") + # 経過日数のグラデーション:(正の値のみの場合)
  #scale_fill_gradient2(low = "hotpink", mid = "white" , high = "#00A968") + # 経過日数のグラデーション:(負の値を含む場合)
  theme(panel.grid.minor = element_blank()) + # 図の体裁
  labs(title = paste0(format(date_from, format = "%Y年%m月%d日"), "からの経過期間"), 
       fill = "日数の差", 
       x = "年-月", y = "日")

日付ごとの経過期間

 基準の日にち(この例だと15日)における経過日数が全て0日で、月末にかけて1日ずつ増えるのを確認できる。月末の日数が異なるので、基準の日にちより前の日にちでは経過日数が異なる。

基準の日が月末付近の場合


カレンダーで確認

 同様に、カレンダー形式で確認する。(色々あって同時期にカレンダーを作ったのでやってみるだけで、特に意味はない。たぶんこっちの方が分かりにくい。)詳しくは「ggplot2でカレンダーを作りたい - からっぽのしょこ」を参照のこと。

・コード(クリックで展開)

 カレンダーの作図用のデータフレームを作成する。

# カレンダー用に経過期間データを整形
calendar_df <- date_df |> 
  dplyr::select(date, day = date_day, d, ymd_label) |> 
  dplyr::bind_rows(
    tibble::tibble(
      # 1日間隔の日付を作成
      date = seq(
        from = (date_from - lubridate::days(1)) |> # (-1は基準日が月初の場合の簡易的対策)
          lubridate::floor_date(unit = "month"), # 基準日の月の初日
        to   = date_from - lubridate::days(1), # 基準日の前日
        by = "day"
      )
    )
  ) |> # 基準日の月のインデックスの計算用
  dplyr::arrange(date) |> # インデックスの計算用
  dplyr::mutate(
    year = date |> 
      lubridate::year() |> 
      as.numeric(), # 経過日の年
    month = date |> 
      lubridate::month() |> 
      as.numeric(), # 経過日の月
    dow_idx   = lubridate::wday(date), # 曜日番号(列インデックス)
    dow_label = lubridate::wday(date, label = TRUE) # 曜日ラベル
  ) |> 
  dplyr::group_by(year, month) |> # インデックスの作成用
  dplyr::mutate(
    cell_idx = dplyr::if_else(
      condition = date >= date_from, 
      true = dplyr::row_number() + head(dow_idx, n = 1) - 1, 
      false = NA_real_
    ), # セルインデックス
    week_idx = dplyr::if_else(
      condition = date >= date_from, 
      true = (cell_idx - 1) %/% 7 + 1, 
      false = NA_real_
    ) # 週番号(行インデックス)
  ) |> 
  dplyr::ungroup() |> 
  dplyr::filter(date >= date_from) # 基準日以前の日付を除去
calendar_df |> 
  dplyr::select(!day) # 資料用に間引き
## # A tibble: 402 × 9
##    date           d ymd_label    year month dow_idx dow_label cell_idx week_idx
##    <date>     <dbl> <chr>       <dbl> <dbl>   <dbl> <ord>        <dbl>    <dbl>
##  1 2022-01-15     0 0年0か月0日  2022     1       7 土              21        3
##  2 2022-01-16     1 0年0か月1日  2022     1       1 日              22        4
##  3 2022-01-17     2 0年0か月2日  2022     1       2 月              23        4
##  4 2022-01-18     3 0年0か月3日  2022     1       3 火              24        4
##  5 2022-01-19     4 0年0か月4日  2022     1       4 水              25        4
##  6 2022-01-20     5 0年0か月5日  2022     1       5 木              26        4
##  7 2022-01-21     6 0年0か月6日  2022     1       6 金              27        4
##  8 2022-01-22     7 0年0か月7日  2022     1       7 土              28        4
##  9 2022-01-23     8 0年0か月8日  2022     1       1 日              29        5
## 10 2022-01-24     9 0年0か月9日  2022     1       2 月              30        5
## # … with 392 more rows

 基準日以前の同月の日付を追加して、日付ごとに曜日番号と週番号(各日付が何曜日であるかと第何週であるか)を計算する。基準日以前の日付は、インデックスを欠損値にするかインデックスの計算後に取り除く。この例ではどちらも行っている。(基準日が月初だった場合は、前月の日付が追加されるが処理上の問題はない。)

 基準日を表示するためのデータフレームを作成する。

# 基準日を格納
date_from_df <- calendar_df |> 
  dplyr::filter(date == date_from) # 基準日のデータを抽出
date_from_df |> 
  dplyr::select(!day) # 資料用に間引き
## # A tibble: 1 × 9
##   date           d ymd_label    year month dow_idx dow_label cell_idx week_idx
##   <date>     <dbl> <chr>       <dbl> <dbl>   <dbl> <ord>        <dbl>    <dbl>
## 1 2022-01-15     0 0年0か月0日  2022     1       7 土              21        3

 カレンダー用の値calendar_dfから基準日date_fromの値を取り出す。

 カレンダー上にヒートマップとラベルを描画して確認する。

# ラベル用の関数を作成
str_year <- function(string) {
  paste0(string, "年")
}
str_month <- function(string) {
  paste0(string, "月")
}

# 経過期間をカレンダー上のヒートマップで可視化
calendar_graph <- ggplot() + 
  geom_tile(data = calendar_df, 
            mapping = aes(x = dow_idx, y = week_idx, fill = d), 
            color = "black") + # 日付セル
  geom_tile(data = date_from_df, 
            mapping = aes(x = dow_idx, y = week_idx), 
            fill = "blue", color = "blue", alpha = 0.1) + # 基準日セル
  geom_label(data = calendar_df, 
             mapping = aes(x = dow_idx-0.5, y = week_idx-0.5, label = day), 
             hjust = 0, vjust = 1, label.padding = unit(0.1, units = "line"), 
             size = 1.5) + # 日付ラベル
  geom_text(data = calendar_df, 
            mapping = aes(x = dow_idx, y = week_idx, label = ymd_label), 
            size = 1.5) + # 経過日数ラベル
  scale_x_continuous(breaks = 1:7, labels = lubridate::wday(1:7, label = TRUE)) + # 曜日軸
  scale_y_reverse(breaks = 1:6) + # 週軸
  scale_fill_gradient(low = "white" , high = "#00A968") + # 経過日数のグラデーション:(正の値のみの場合)
  #scale_fill_gradient2(low = "hotpink", mid = "white" , high = "#00A968") + # 経過日数のグラデーション:(負の値を含む場合)
  facet_wrap(year ~ month, labeller = labeller(year = str_year, month = str_month)) + # 月ごとに分割
  theme(panel.grid.minor = element_blank()) + # 図の体裁
  labs(title = paste0(format(date_from, format = "%Y年%m月%d日"), "からの経過期間"), 
       fill = "経過日数", 
       x = "曜日", y = "週")
calendar_graph

日付ごとの経過期間

 geom_tile()geome_label()でカレンダー形式でヒートマップを描画する。

事前・事後の日ごとに経過日数

 ここまでは、指定した日以降の日付を扱った。最後は、指定した日以前の日付も含めて経過期間を求める。

期間計算

 基準となる日と期間を設定して、日ごとに経過期間を計算する。

# 基準日を指定
date_from <- "2022-01-15" |> 
  lubridate::as_date()

# 期間を指定
date_min <- "2020-12-10" |> 
  lubridate::as_date()
date_max <- "2022-03-10" |> 
  lubridate::as_date()

# 基準日以前・以降の日ごとに経過期間を計算
date_df <- tibble::tibble(
  # 1日間隔の日付を作成
  date = seq(from = date_min, to = date_max, by = "day")
) |> 
  dplyr::mutate(
    # 期間計算用の値を作成
    date_day = date |> 
      lubridate::day() |> 
      as.numeric(), # 経過日の日にち
    from_day = date_from |> 
      lubridate::day() |> 
      as.numeric(), # 基準日の日にち
    last_day = date |> 
      lubridate::rollforward() |> 
      lubridate::day() |> 
      as.numeric(),# 基準日の月の末日
    pre_last_day = date |> 
      lubridate::rollback() |> 
      lubridate::day() |> 
      as.numeric(), # 経過日の前月の末日
    next_last_day = date |> 
      lubridate::rollforward(roll_to_first = TRUE) |> 
      lubridate::rollforward() |> 
      lubridate::day() |> 
      as.numeric(), # 経過日の翌月の末日
    # 経過期間を計算
    y = dplyr::case_when(
      date >= date_from ~ lubridate::interval(start = date_from, end = date) |> 
        lubridate::time_length(unit = "year") |> 
        floor(), 
      date < date_from ~ lubridate::interval(start = date, end = date_from) |> 
        lubridate::time_length(unit = "year") |> 
        floor() * (-1), 
    ), # 経過年数
    m = dplyr::case_when(
      date >= date_from ~ lubridate::interval(start = date_from, end = date) |> 
        lubridate::time_length(unit = "month") |> 
        floor() %% 12, 
      date < date_from ~ lubridate::interval(start = date, end = date_from) |> 
        lubridate::time_length(unit = "month") |> 
        floor() %% 12 * (-1)
    ), # 経過月数
    d = dplyr::case_when(
      (date >= date_from & from_day >  date_day & from_day <  pre_last_day) ~ pre_last_day - from_day + date_day, # 前月の途中から当月の途中まで
      (date >= date_from & from_day >  date_day & from_day >= pre_last_day) ~ date_day, # 当月の頭から当月の途中まで
      (date >= date_from & from_day <= date_day) ~ date_day - from_day, # 当月の途中から当月の途中まで
      (date <  date_from & from_day <  date_day & from_day <= next_last_day) ~ date_day - last_day - from_day, # 当月の途中から翌月の途中まで
      (date <  date_from & from_day <  date_day & from_day >  next_last_day) ~ date_day - last_day - next_last_day-1, # 当月の途中から翌々月の頭まで
      (date <  date_from & from_day >= date_day & from_day <= last_day) ~ date_day - from_day, # 当月の途中から当月の途中まで
      (date <  date_from & from_day >= date_day & from_day >  last_day) ~ date_day - last_day-1 # 当月の途中から翌月の頭まで
    ), # 経過日数
    days = lubridate::interval(start = date_from, end = date) |> 
      lubridate::time_length(unit = "day"), # 総経過日数
    ymd_label = paste0(y, "年", m, "か月", d, "日") # 経過期間ラベル
  )
date_df |> 
  dplyr::select(!c(date_day, from_day)) # 資料用に間引き
## # A tibble: 456 × 9
##    date       last_day pre_last_day next_last_day     y     m     d  days
##    <date>        <dbl>        <dbl>         <dbl> <dbl> <dbl> <dbl> <dbl>
##  1 2020-12-10       31           30            31    -1    -1    -5  -401
##  2 2020-12-11       31           30            31    -1    -1    -4  -400
##  3 2020-12-12       31           30            31    -1    -1    -3  -399
##  4 2020-12-13       31           30            31    -1    -1    -2  -398
##  5 2020-12-14       31           30            31    -1    -1    -1  -397
##  6 2020-12-15       31           30            31    -1    -1     0  -396
##  7 2020-12-16       31           30            31    -1     0   -30  -395
##  8 2020-12-17       31           30            31    -1     0   -29  -394
##  9 2020-12-18       31           30            31    -1     0   -28  -393
## 10 2020-12-19       31           30            31    -1     0   -27  -392
## # … with 446 more rows, and 1 more variable: ymd_label <chr>

 基準日date_from、期間の開始日date_minと最終日date_maxを日付型の値で指定する。
 「日ごとの経過日数」のときと同様に、date_minからdate_maxまでの日付を作成して、経過日の日にち列date_day、基準日の日にち列from_day、経過日の月末の日にち列last_day、経過日の前月末の日にち列pre_last_day、経過日の翌月末の日にち列next_last_dayを作成しておく。

 「経過日」が「基準日」以降(datedate_from以上)の場合は、基準日date_fromから各経過日dateまでの経過年数列y、経過月数列mを計算する。
 「経過日」が「基準日」以前(datedate_from未満)の場合は、各経過日dateから基準日date_fromまでの経過年数列y、経過月数列mを計算して、それぞれ負の値にする(-1を掛ける)。

 経過日数列dは、「基準日」と「経過日」、また「基準日の日にち」と「経過日の日にち」または「経過日の前月末の日にち」との大小関係によって場合分けして計算する。

  • 「経過日」が「基準日」以降(datedate_from以上)または同じ場合は、「日ごとの経過日数」のときと同様にして日数を求める。
    • 「経過日の日にち」から「基準の日にち」まで(form_daydate_dayより大きい場合)の日数を、さらに2つの場合に応じて計算する。
      • 「基準の日にち」が「前月に有る」(from_daypre_last_dayより小さい)場合は、「前月の基準の日から月末までの日数」(pre_last_day - from_day)と「当月の経過日数」(date_day)の和である。
      • 「基準の日にち」が「前月に無い・前月末の日にち」(from_daypre_last_day以上)の場合は、基準の日を月末とみなして、「当月の経過日数」(date_day)である。
    • 「基準の日にち」から「経過日の日にち」まで(form_daydate_day以下の場合)の日数は、「当月の基準の日から経過日までの日数」(date_day - from_day)である。
  • 「経過日」が「基準日」以前(datedate_from未満)の場合は、負の日数を求める。
    • 「経過日の日にち」から「基準の日にち」まで(form_daydate_dayより小さい場合)の負の日数は、「当月の経過日から月末までの負の日数」(date_day - last_day - 1)と「翌月の基準の日までの負の日数」(1 - from_day)の和である。
    • ただし「翌月に基準の日にちが無い」(from_daynext_last_dayより大きい)場合は、基準の日を翌々月の初日とみなして、「翌月の初日から翌々月の初日までの負の日数」(1 - (next_last_day+1))との和である。
    • 「基準の日にち」から「経過日の日にち」まで(form_daydate_day以上の場合)の負の日数は、「経過日から当月の基準の日までの負の日数」(date_day - from_day)である。
    • ただし「当月に基準の日にちが無い」(from_daylast_dayより大きい)場合は、基準の日を翌月の初日とみなして、「経過日から翌月の初日までの負の日数」(date_day - (last_day+1))である。

 経過日数列y, m, dを用いて、経過日数ラベルを作成してymd_label列とする。

 以上で、経過期間を計算できた。続いて、計算結果を確認する。

ヒートマップで確認

 ヒートマップとラベルで計算結果を確認する。

 「ひと年のカレンダー」のときのコードで、作図用のデータフレームを作成して、ヒートマップとラベルで経過期間を確認する。

# 確認
heatmap_df
## # A tibble: 456 × 6
##    date           d  days ymd_label        day year_month
##    <date>     <dbl> <dbl> <chr>          <dbl> <fct>     
##  1 2020-12-10    -5  -401 -1年-1か月-5日    10 2020-12   
##  2 2020-12-11    -4  -400 -1年-1か月-4日    11 2020-12   
##  3 2020-12-12    -3  -399 -1年-1か月-3日    12 2020-12   
##  4 2020-12-13    -2  -398 -1年-1か月-2日    13 2020-12   
##  5 2020-12-14    -1  -397 -1年-1か月-1日    14 2020-12   
##  6 2020-12-15     0  -396 -1年-1か月0日     15 2020-12   
##  7 2020-12-16   -30  -395 -1年0か月-30日    16 2020-12   
##  8 2020-12-17   -29  -394 -1年0か月-29日    17 2020-12   
##  9 2020-12-18   -28  -393 -1年0か月-28日    18 2020-12   
## 10 2020-12-19   -27  -392 -1年0か月-27日    19 2020-12   
## # … with 446 more rows

日付ごとの経過期間

 基準日以前の日においても、基準の日にち(この例だと15日)における経過日数が全て0日なのを確認できる。月初にかけて1日ずつ減る。月末の日数が異なるので、基準の日にちより後の日にちでは経過日数が異なる。

基準の日が月末付近の場合


カレンダーで確認

 一応こっちもやっておく。

・コード(クリックで展開)

 カレンダーの作図用のデータフレームを作成する。

# カレンダー用に経過期間データを整形
calendar_df <- date_df |> 
  dplyr::select(date, day = date_day, d, ymd_label) |> 
  dplyr::bind_rows(
    tibble::tibble(
      # 1日間隔の日付を作成
      date = seq(
        from = (date_min - lubridate::days(1)) |> # (-1は期間の開始日が月初の場合の簡易的対策)
          lubridate::floor_date(unit = "month"), # 期間の開始日の月の初日
        to   = date_min - lubridate::days(1), # 期間の開始の前日
        by = "day"
      )
    )
  ) |> # 基準日の月のインデックスの計算用
  dplyr::arrange(date) |> # インデックスの計算用
  dplyr::mutate(
    year = date |> 
      lubridate::year() |> 
      as.numeric(), # 経過日の年
    month = date |> 
      lubridate::month() |> 
      as.numeric(), # 経過日の月
    dow_idx   = lubridate::wday(date), # 曜日番号(列インデックス)
    dow_label = lubridate::wday(date, label = TRUE) # 曜日ラベル
  ) |> 
  dplyr::group_by(year, month) |> # インデックスの作成用
  dplyr::mutate(
    cell_idx = dplyr::if_else(
      condition = date >= date_min, 
      true = dplyr::row_number() + head(dow_idx, n = 1) - 1, 
      false = NA_real_
    ), # セルインデックス
    week_idx = dplyr::if_else(
      condition = date >= date_min, 
      true = (cell_idx - 1) %/% 7 + 1, 
      false = NA_real_
    ) # 週番号(行インデックス)
  ) |> 
  dplyr::ungroup() |> 
  dplyr::filter(date >= date_min) # 基準日以前の日付を除去
calendar_df |> 
  dplyr::select(!day) # 資料用に間引き
## # A tibble: 456 × 9
##    date           d ymd_label     year month dow_idx dow_label cell_idx week_idx
##    <date>     <dbl> <chr>        <dbl> <dbl>   <dbl> <ord>        <dbl>    <dbl>
##  1 2020-12-10    -5 -1年-1か月-…  2020    12       5 木              12        2
##  2 2020-12-11    -4 -1年-1か月-…  2020    12       6 金              13        2
##  3 2020-12-12    -3 -1年-1か月-…  2020    12       7 土              14        2
##  4 2020-12-13    -2 -1年-1か月-…  2020    12       1 日              15        3
##  5 2020-12-14    -1 -1年-1か月-…  2020    12       2 月              16        3
##  6 2020-12-15     0 -1年-1か月0…  2020    12       3 火              17        3
##  7 2020-12-16   -30 -1年0か月-3…  2020    12       4 水              18        3
##  8 2020-12-17   -29 -1年0か月-2…  2020    12       5 木              19        3
##  9 2020-12-18   -28 -1年0か月-2…  2020    12       6 金              20        3
## 10 2020-12-19   -27 -1年0か月-2…  2020    12       7 土              21        3
## # … with 446 more rows

 基準日date_fromではなく期間の開始日date_minを用いて、「ひと年のカレンダー」のときと同様に処理する。

 「ひと年のカレンダー」のときのコードで、カレンダー上にヒートマップとラベルを描画して確認する。

日付ごとの経過期間


 この記事では、経過期間を計算した。

おわりに

 もう色々と大変でした。解説文を読んで意味が伝わなかった方々すみません。頑張って書きましたが自分でも何を言っているのか全然分かりません。

 そもそもは↓の記事にて、各メンバーの活動歴を計算する必要がありました。

www.anarchive-beta.com

 この記事の初稿の段階では、なんとも酷いコードでしたが(誤差があってもたぶん1日くらいで)なんとか計算できました。このときは、いくつかピックアップして年齢計算サイトに手打ちして計算結果を確認しました。

 それから1年弱が経って、もう少しスマートにできないものかと再挑戦しました。条件分岐の数は相変わらずですが、これでもかなりマシな計算処理になったと思います。確認方法についても、1年分を1日ごとに計算すれば間違いが分かると気付いてヒートマップにしました。
 途中嫌になってカレンダーを作り始めて別記事が生えました。特に意味はなかったのですが少しウケて良かったです。

 ややこしい処理は説明もややこしくて解説するのに四苦八苦しました。解説用に日付の設定を変えてたら計算ミスが次々発生して(実装ミスに気付けたこと自体は良かった)あぁもう日付計算って本当に面倒臭い。数式弄りの方がまだ簡単に思えてきた。競プロとかで鍛えればこの辺の脳筋トレになるのかな。
 英語力が皆無なので変数名に悩むのはいつものことですが、今回は日本語での呼び方も思い付かず悩みました。基準日はいいとしても経過日とは?なんと言えばいいんだ。それと「ある日付m月d日からn日後のm月d+n日の日にち」みたいな言い回しのヤツ、単にd+nを言いたいだけなんだ。「1日」は(ついたち)なのか(いちにち)なのか、「n日」は日付なのか日数なのか、とにかく表現がああ。

 せめて使う予定のない負の日数なるものに手を出さなければよかった。つい興味が出てしまったんです。
 ところで、まさか関数一発で計算できたりなんてことないですよね???できたら嬉しいけど泣ける…

 最後にこの曲を聴きたい。

 (この記事の図の配色はハロプロ由来なのにエンディングはエビ中曲とは最後までぐだぐだだ。)