9 日付型データ

R で日付データを扱う際には、他のデータ型のデータを扱うときよりも注意が必要です。本章では、日付データを扱いやすくするツールや例を紹介します。練習すれば日付データを簡単に扱えるようになりますし、lubridate のような便利なパッケージもあります。

R に未加工のデータをインポートすると、日付データが文字型として解釈されることがあります。その場合、時系列の作成や時間間隔の計算など、日付データの一般的な操作が行えません。さらに日付には様々な書式があるため、日付のどの部分が何を表しているのか(月、日、時間など)を R に伝える必要があります。

R における日付データは独自のデータ型である 日付型(Date)です。また、日付と時刻両方を持つオブジェクトを格納するデータ型もあるので、注意が必要です。日付と時刻の両方を格納するオブジェクトは、正式には POSIXt 型, POSIXct 型, POSIXlt 型 と呼ばれます(三つの型がどのように違うのかは重要ではありません)。これらの三つの型は、非公式に 日時型(datetime)と呼ばれています。

  • 日付データが含まれる場合、それを R に伝えることが重要です。
  • 日付はオブジェクトクラスであり、扱うのが難しい場合があります。
  • ここでは、日付の列を日付型(Date)に変換するいくつかの方法を紹介します。

9.1 準備

パッケージの読み込み

以下のコードを実行すると、分析に必要なパッケージが読み込まれます。このハンドブックでは、パッケージを読み込むために、pacman パッケージの p_load() を主に使用しています。p_load() は、必要に応じてパッケージをインストールし、現在の R セッションで使用するためにパッケージを読み込む関数です。また、すでにインストールされたパッケージは、R の基本パッケージである baselibrary() を使用して読み込むこともできます。R のパッケージについては、R の基礎 の章を参照してください。

# パッケージがインストールされているか確認し、必要に応じてパッケージをインストールし、ロードする

pacman::p_load(
  lubridate,  # 日付の扱いと変換のための一般的なパッケージ  
  linelist,   # 厄介な日付を「推測」する関数を持つ
  aweek,      # 日付を週に、週を日付に変換する別のオプション
  zoo,        # 日付と時間に関する追加の関数
  tidyverse,  # データマネジメントと可視化  
  rio)        # データのインポートとエクスポート

データのインポート

エボラ出血熱の流行をシミュレートしたデータセットをインポートします。お手元の環境でこの章の内容を実行したい方は、ハンドブックとデータのダウンロード の章を参照してください。ここではデータのファイルが作業ディレクトリにあると仮定し、ファイルパスにはサブフォルダを指定しません。

linelist <- import("linelist_cleaned.xlsx")

9.2 現在の日付

コンピュータの「システム」における現在の日付や日時を取得するには、base R を使って次のように行います。

# システムの日付を取得(日付型:DATE)
Sys.Date()
## [1] "2022-10-18"
# システム時刻の取得(日時型:DATETIME)
Sys.time()
## [1] "2022-10-18 11:54:31 UTC"

lubridate パッケージでは、それぞれを today()now() で返すことができます。 date() は現在の日付と時刻を、曜日と月名とともに返します。

9.3 日付型(Date)に変換

データセットを R にインポートすると、日付列の値が「1989/12/30」、「05/06/2014」、「13 Jan 2020」のようになることがあります。このような場合、R はこれらの値を文字として扱っている可能性が高いです。これらの値が日付であること、そしてその日付のフォーマット(どの部分が日で、どこが月で、どこが年であるかなど)を R に伝える必要があります。

R に伝えることで、このような値を日付型(Date)に変換します。日付型(Date)に変更された日付は、システム内部では数値(「起点日」の 1970 年 1 月 1 日からの日数)として保存されています。日付を数値として扱うことはあまりありませんが、これにより日付を連続変数として扱い、日付間の距離を計算するなどの特殊な操作が可能になります。

デフォルトでは、日付型(Date)の値は YYYY-MM-DD で表示されます。このセクションでは、日付の値の表示方式を変更する方法について説明します。

以下では、列を文字型(character)から日付型(Date)型に変換する 2 つのアプローチを紹介します。

ヒント: class(linelist$date_onset) のように、base R の class() で現在のデータ型を確認することができます。

base R

as.Date() (“D” が大文字であることに注意)は、オブジェクトや列を日付型(Date)に変換する標準的な base R の関数です。

as.Date() を使用する際には、以下が必要です。

  • 文字型(character)としてインポートされた日付データの現在の形式を指定します。または、日付を数値として扱う場合は、元の日付の形式を指定します(9.4 Excel における日付 のセクションを参照)。
  • 文字型(character)の日付データに使用する場合、すべての日付値は同じフォーマットでなければなりません(そうでない場合は、linelist パッケージの guess_dates() をお試しください)。

まずbase R の class() で列のデータ型を確認してください。データ型がわからない場合や表示されたデータ型が見慣れない場合(例えば “POSIXct”と表示される場合など)は、まず as.character() で文字型(character)に変換し、その後に日付型(Date)に変換するのが最も簡単です。

次にas.Date() 内で、format = 引数を使って、文字型(character)の日付データの現在のフォーマット、つまり、どの文字が月、日、年を表すのか、どのように分けられているのかを R に伝えます。現在のフォーマットがすでに R の標準的な日付フォーマット(“YYYY-MM-DD” または “YYYY/MM/DD”)である場合は、format = 引数は必要ありません。

format = には、以下の特殊な “strptime” の略語を使用して、現在の日付フォーマットを表す文字列を(引用符で囲んで)指定します。例えば、“24/04/1968” のように、現時点での日付データが ’“DD/MM/YYYY” のフォーマットである文字型(character)である場合、format = "%d/%m/%Y" を使用して値を日付型(Date)に変換します。フォーマットを引用符で囲む必要があることに注意してください。また、スラッシュやダッシュも忘れずにつけてくださいね!

# Date 型に変換
linelist <- linelist %>% 
  mutate(date_onset = as.Date(date_of_onset, format = "%d/%m/%Y"))

以下のリストは、strptime のほぼすべての略語を含んでいます。ここにない略語をご覧になりたい方は、?strptime を実行すると、すべての略語を含むリストを見ることができます。

%d = 月の日にち(5、17、28 など)
%j = 年の日にち(ユリウス通日 001-366)
%a = 曜日の省略形(Mon、Tue、Wed など)
%A = 完全な曜日表示(Monday、Tuesday など)
%w = 曜日番号(0-6、日曜日が 0)
%u = 曜日番号(1-7、月曜日が 1)
%W = 週の番号(00-53、月曜日が週の始まり)
%U = 週の番号(01-53、日曜日が週の始まり)
%m = 月の番号(例:01、02、03、04)
%b = 月の省略形(Jan、Feb など)
%B = 完全な月表示(January、February など)
%y = 2桁の年(例:89)
%Y = 4桁の年(例:1989)
%h = 時間(24 時間表示)
%m = 分
%s = 秒
%z = GMT(グリニッジ標準時)からの差
%Z = タイムゾーン(文字型)

ヒント:as.Date()format = 引数は、R にコマンド実行後の日付のフォーマットを伝えるためではなく、コマンド実行前の日付を識別するために指定します。

ヒント:format = の引数には、対象の日付に使われているセパレータ(例えば、/ や - 、またはスペース)を含めることを忘れないでください。

日付が日付型(Date)に変換されると、YYYY-MM-DD というデフォルトの標準フォーマットで表示されます。

lubridate

文字型のオブジェクトを日付に変換するには、lubridate パッケージを使用すると簡単です。lubridate パッケージは base R よりもシンプルで一貫性があり、日付と時刻を扱うために開発されたtidyverse のパッケージです。lubridate は日付と時刻の最も標準的な手法とされ、常に推奨されます。

lubridate パッケージは、文字型オブジェクトを日付に変換するためのヘルパー関数をいくつか提供しています。このヘルパー関数は、as.Date() でフォーマットを指定ことに比べ、直観的かつ柔軟に文字型オブジェクトを変換することができます。ヘルパー関数は、柔軟な日付フォーマットに対応し、さまざまなセパレータを使用できることに加え、日付フォーマットの略語に由来した日付の同義語(例えば、January を意味する “01” や “Jan”)を使用することもできます。

# lubridate をインストールし、読み込む
pacman::p_load(lubridate)

ymd() は、対象となる日付の値を年、月、日の順に柔軟に変換します。

# 「年月日」のフォーマットで日付を読み込み
ymd("2020-10-11")
## [1] "2020-10-11"
ymd("20201011")
## [1] "2020-10-11"

mdy() は、対象となる日付の値を月、日、年の順に柔軟に変換します。

# 「月日年」のフォーマットで日付を読み込み
mdy("10/11/2020")
## [1] "2020-10-11"
mdy("Oct 11 20")
## [1] "2020-10-11"

dmy() は、対象となる日付の値を日、月、年の順に柔軟に変換します。

# 「日月年」のフォーマットで日付を読み込み
dmy("11 10 2020")
## [1] "2020-10-11"
dmy("11 October 2020")
## [1] "2020-10-11"

パイプ(%>%)を使って文字型(character)の列から日付型(Date)の列への変換を lubridate で行うと、以下のようになります。

linelist <- linelist %>%
  mutate(date_onset = lubridate::dmy(date_onset))

コマンド実行後、class() を実行して列のデータ型を確認します。

# 列のデータ型を確認
class(linelist$date_onset)  

日付が日付型(Date)に変換されると、YYYY-MM-DD というデフォルトの標準フォーマットで表示されます。

上記の関数は、年が 4 桁のときに最も期待通りに機能します。2 桁の年は、lubridate が世紀として推測するため、予想外の結果になることがあります。

2 桁の年を(すべて同じ世紀の 4 桁の年に変換するには、文字型(character)に変換した後、 stringr パッケージの str_glue() を用いて、既存の数字と接頭辞(prefix)を組み合わせ(文字型・文字列型データ の章を参照)、その後日付型(Date)に変換します。

two_digit_years <- c("15", "15", "16", "17")
str_glue("20{two_digit_years}")
## 2015
## 2015
## 2016
## 2017

列の結合

複数の数字型(numeric)の列を一つの日付型(Date)の列にまとめるには、lubridate 関数の make_date()make_datetime() を利用します。例えば、データフレーム linelist に 数値列 onset_day, onset_month, onset_year がある場合、以下のようなコマンドを実行してください。

linelist <- linelist %>% 
  mutate(onset_date = make_date(year = onset_year, month = onset_month, day = onset_day))

9.4 Excel における日付

多くのソフトウェアでは、内部では日付を数字として保存します。R は 1970 年 1 月 1 日を起点として日付を格納します。したがって、as.numeric(as.Date("1970-01-01)) を実行すると 0 となります。

Microsoft Excel では、お使いの OS に応じて、Windows の場合は 1899年12月30日、Mac の場合は 1904 年 1 月 1 日を起点として日付が保存されます。詳しくは Microsoft の公式ドキュメント をご覧ください。

このように、Excel では日付が数値として保存されているため、Excel ファイルを R でインポートすると、日付が文字ではなく数値としてインポートされることがよくあります。Excel からインポートしたデータセットで、日付が “41369” のような数字や文字として表示されている場合は as.Date() (または lubridateas_date())を使って変換しますが、先述のようにフォーマットを指定する代わりに、Excel の日付の起点日を引数 origin = に指定します。

これは、Excel の日付が文字型(character)として R に保存されている場合には機能しないため、日付が数字型(numeric)であることを必ず確認してください。

注釈:起点日は、R の標準の日付形式(YYYY-MM-DD)で入力してください。

# Excel の数字の日付を変換する際、Excel の「起点日」を入力する例
data_cleaned <- data %>% 
  mutate(date_onset = as.numeric(date_onset)) %>%   # 数字型であることを確認
  mutate(date_onset = as.Date(date_onset, origin = "1899-12-30")) # Excelの起点日で日付に変換

9.5 厄介な日付

linelist パッケージの guess_dates() は、複数の異なる形式の日付を含む「厄介な」日付列を読み込んで、その日付を標準的な形式に変換してくれます。guess_dates() については、こちら でさらに詳しく学ぶことができます。R 4.0.2 の CRAN に guess_dates() がない場合は、 pacman::p_load_gh("reconhub/linelist") でインストールしてください。

例えば guess_dates は、文字の日付”03 Jan 2018”, “07/03/1982”, “08/20/85”のベクトルを、“2018-01-03”, “1982-03-07”, “1985-08-20”のように、一律で日付(Date)型に変換します。

linelist::guess_dates(c("03 Jan 2018",
                        "07/03/1982",
                        "08/20/85"))
## [1] "2018-01-03" "1982-03-07" "1985-08-20"

guess_dates() のオプションの引数には、以下があります。

  • error_tolerance - 日付が特定できない行が許容される割合 (デフォルトは 0.1、つまり10%)
  • last_date - 最も新しい有効な日付(デフォルトは現在の日付)
  • first_date - 最も古い有効な日付(デフォルトは last_date の 50 年前)
# dater_onset 列に guess_dates を使用した例
linelist <- linelist %>%                 # linelist というデータセット
  mutate(
    date_onset = linelist::guess_dates(  # "linelist" パッケージの guess_dates()
      date_onset,
      error_tolerance = 0.1,
      first_date = "2016-01-01"
    )

9.6 日時型(datetime)に変換

先に述べたように、R では日付と時間の両方の情報を含む、日時型(datetime)もサポートしています。日付型(Date)と同様に、文字型(character)オブジェクトから日時型(datetime)オブジェクトに変換する必要があります。

日付と時刻の変換

標準的な日時型(datetime)は、最初に日付があり、その後に時間要素が続く形式です( 01 Jan 2020, 16:30 など)。日付と同様に、このフォーマットには様々な方法があり、また、提供できる精度(時、分、秒)のレベルも様々です。

これらの文字列を日時型(datetime)に変換するための lubridate ヘルパー関数も存在します。これらの関数は、日付ヘルパー関数を拡張したもので、末尾に _h(時のみを指定)、_hm(時と分を指定)、_hms(時、分、秒を指定)を付けます(例: dmy_hms())。以下のように使用します。

時間のみの日時データを日時型(datetime)に変換する

ymd_h("2020-01-01 16hrs")
## [1] "2020-01-01 16:00:00 UTC"
ymd_h("2020-01-01 4PM")
## [1] "2020-01-01 16:00:00 UTC"

時と分を含む日時データを日時型(datetime)に変換する

dmy_hm("01 January 2020 16:20")
## [1] "2020-01-01 16:20:00 UTC"

時、分、秒を含む日時データを日時型(datetime)に変換する

mdy_hms("01 January 2020, 16:20:40")
## [1] "2020-01-20 16:20:40 UTC"

タイムゾーンを指定することもできますが、有効ではありません。タイムゾーンについては、この章の後半、「9.11 日付・タイムゾーンの変換」のセクションを参照してください。

mdy_hms("01 January 2020, 16:20:40 PST")
## [1] "2020-01-20 16:20:40 UTC"

データフレームを扱う場合は、stringr パッケージの str_glue()lubridate パッケージの関数を用いて、日付の列と時間の列を組み合わせて日時型(datetime)の列を作成できます。stringr の詳細については、文字型・文字列型データ の章を参照してください。

以下では、linelist データフレームを例として使用します。linelist データフレームには、 “hours : minutes” という時間と分がコロン(:)で区切られているフォーマットの列があり、これを日時型(datetime)に変換していきます。

  1. まず、“hours : minutes” 列の欠損値を列の中央値で埋め、「きれいな」入院時刻の列(time_admission_clean)を作成します。これは lubridate が欠損値を処理できないためです。time_admission_clean 列を date_hospitalisation 列に結合し、 最後に ymd_hm() 関数を使って日時型(datetime)に変換します。
# パッケージ
pacman::p_load(tidyverse, lubridate, stringr)

# time_admission 列は hours:minutes というフォーマット
linelist <- linelist %>%
  
  # 入院時刻が欠損している場合は、中央値の入院時刻を割り当てる
  mutate(
    time_admission_clean = ifelse(
      is.na(time_admission),         # 時刻が欠損している場合、
      median(time_admission),        # 中央値を割り当て、
      time_admission                 # 欠損していない場合はそのままにする
  ) %>%
  
    # str_glue() を使用して、日付と時刻の列を結合し、1つの文字型の列を作成
    # ymd_hm() を使って日時型に変換
  mutate(
    date_time_of_admission = str_glue("{date_hospitalisation} {time_admission_clean}") %>% 
      ymd_hm()
  )

時間のみの変換

“hours : minutes” というように、対象となる日付データが文字型(character)で時間(時、分)しか含まれていない場合は、base R の strptime() を使って時間として変換・操作することができます。例えば、2 つの時間の差を求めるには、

# 直接、文字で時間を入力
time1 <- "13:45" 
time2 <- "15:20"

# 日時型(datetime)に変換した時刻
time1_clean <- strptime(time1, format = "%H:%M")
time2_clean <- strptime(time2, format = "%H:%M")

# 差分はデフォルトでは "difftime" クラスだが、ここでは数値に変換
as.numeric(time2_clean - time1_clean)   # 時間単位( hour )での差分
## [1] 1.583333

ただし、日付の値が指定されていない場合は、今日の日付として変換されます。文字型の日付列と文字型の時刻列を組み合わせるには、上のセクションの stringr の使用方法を参照してください。strptime() について詳しくはこちらをご覧ください。

一桁の数字を二桁に変換するには(例えば、時間や分を先頭のゼロで”埋めて” 2 桁にするなど)、文字型・文字列型データの章の「文字列を伸長する」のセクションをご覧ください。

時間の抽出

lubridatehour(), minute(), second() で時刻の要素を抽出できます。

ここでは、時刻を抽出して一日の区分で分類する例を示します。 time_admission という入院時刻を表す列 は、“HH:MM” というフォーマットの文字型(character)です。まず、上述した strptime() を使って文字を日時型(datetime)に変換します。次に hour() で時間を抽出すると、0 ~ 24 の数値が返されます。最後に、case_when() を用いて time_period という列を作成し、入院時刻に基づいて午前(Morning)、午後(Afternoon)、夕方(Evening)、夜(Night)の 4 つに分類します。

linelist <- linelist %>%
  mutate(hour_admit = hour(strptime(time_admission, format = "%H:%M"))) %>%
  mutate(time_period = case_when(
    hour_admit > 06 & hour_admit < 12 ~ "Morning",
    hour_admit >= 12 & hour_admit < 17 ~ "Afternoon",
    hour_admit >= 17 & hour_admit < 21 ~ "Evening",
    hour_admit >=21 | hour_admit <= 6 ~ "Night"))

case_when() については、データのクリーニングと主要関数 の章をご覧ください。

9.7 日付の操作

lubridate は他にも様々な機能があります。例えば、日付や日時から一部を抽出日付の演算日付の間隔を計算などです。

まず、例に用いる日付を定義します。

# 日時型(Date)のオブジェクトを作成
example_date <- ymd("2020-03-01")

日付成分の抽出

月、日、曜日などの共通する部分を抽出します。

month(example_date)  # 月番号
## [1] 3
day(example_date)    # 日にち
## [1] 1
wday(example_date)   # 曜日番号(1-7)
## [1] 1

また、日時型(datetime)のオブジェクトや列から時刻を抽出できます。これは、入院時刻の分布を見たい場合に便利です。

example_datetime <- ymd_hm("2020-03-01 14:45")

hour(example_datetime)     # 時の抽出
minute(example_datetime)   # 分の抽出
second(example_datetime)   # 秒の抽出

週を抽出するにはいくつかオプションがあります。以下の「疫学週」のセクションを参照してください。

特定の表記で日付を表示したい場合(「Jan 2020」や「Thursday 20 March」、「Week 20, 1977」など)は、次の「日付の表示」のセクションで説明するように、より柔軟に対応することができます。

日付の計算

日数や週数を足すには、lubridate の関数を使用します。

# 日付に3日分足す
example_date + days(3)
## [1] "2020-03-04"
# 日付に7週分足し、2日分引く
example_date + weeks(7) - days(2)
## [1] "2020-04-17"

日付の間隔

日付の間隔は、以下の方法で計算できます。

  1. 両方の日付が日付型(Date)であることを確認する
  2. 引き算で日付の差 “difftime” を返す
  3. 必要に応じて、結果を数字型(numeric)に変換する

以下では、2 つの日付の間隔を計算しています。日付型(Date)の値に減算記号(マイナス)を使用して、間隔を求められます。ただし、返される値のデータ型は “difftime” であり、数値に変換する必要があります。

# example_dateから2020年2月20日までの間隔を求める 
output <- example_date - ymd("2020-02-20")
output    # 出力
## Time difference of 10 days
class(output)
## [1] "difftime"

“difftime” に対して追加の操作を行うには、as.numeric() で数字型(numeric)に変換します。

パイプ(%>%)によって、これらを一連の流れで操作できます。

pacman::p_load(lubridate, tidyverse)   # パッケージを読み込む

linelist <- linelist %>%
  
  # dmy フォーマットを指定して、発症日を文字から日付オブジェクトに変換
  mutate(date_onset = dmy(date_onset),
         date_hospitalisation = dmy(date_hospitalisation)) %>%
  
  # 3 月に発症していないケースをすべて除外
  filter(month(date_onset) == 3) %>%
    
  # 発症から入院までの日数の差を求める
  mutate(days_onset_to_hosp = date_hospitalisation - date_of_onset)

データフレームでは、上記の日付のいずれかが欠損していると操作が失敗し、数値ではなく NA が表示されます。この列を計算に使用する場合は、以下のように必ず na.rm = 引数を TRUE に設定してください。

# データが欠損していない症例について、発症から入院までの日数の中央値を算出
median(linelist_delay$days_onset_to_hosp, na.rm = T)

9.8 日付の表示

日付を正しいデータ型にした後、「2018-01-05」ではなく「Monday 05 January」など、データそのものとは異なる表示にしたいことがあります。また表示されている日付の要素でグループ化し、表示を調整することもあります(例:年と月でグループ化)。

format()

日付の表示を調整するには、base R に含まれている関数である format() を使用します。この関数を使用する際は、“%” で始まる strptime 略語( as.Date() で使用されるのと同じ構文)を用いて、希望の出力フォーマットを文字列(引用符で囲まれたもの)で指定します。以下は、一般的な略語の例です。

注意:format() を使用すると、値が文字型(character)に変換されます。そのため、データ分析や処理が終わった後に使用するか、または、表示のみに使用されます。 strptime のすべての略語の一覧は、?strptime を実行してご覧ください。

%d = 月の日にち(5、17、28 など)
%j = 年の日にち(ユリウス通日 001-366)
%a = 曜日の省略形(Mon、Tue、Wed など)
%A = 完全な曜日表示(Monday、Tuesday など)
%w = 曜日番号(0-6、日曜日が 0)
%u = 曜日番号(1-7、月曜日が 1)
%W = 週の番号(00-53、月曜日が週の始まり)
%U = 週の番号(01-53、日曜日が週の始まり)
%m = 月の番号(例:01、02、03、04)
%b = 月の省略形(Jan、Feb など)
%B = 完全な月表示(January、February など)
%y = 2桁の年(例:89)
%Y = 4桁の年(例:1989)
%h = 時間(24 時間表示)
%m = 分
%s = 秒
%z = GMT(グリニッジ標準時)からの差
%Z = タイムゾーン(文字型)

以下は、今日の日付のフォーマットの例です。

# フォーマットした今日の日付
format(Sys.Date(), format = "%d %B %Y")
## [1] "18 October 2022"
# 日付と時間を簡単に取得する(デフォルトのフォーマット)
date()
## [1] "Tue Oct 18 11:54:31 2022"
# str_glue() を使って、フォーマットした日付、時刻、タイムゾーンを組み合わせる
str_glue("{format(Sys.Date(), format = '%A, %B %d %Y, %z  %Z, ')}{format(Sys.time(), format = '%H:%M:%S')}")
## Tuesday, October 18 2022, +0000  UTC, 11:54:31
# フォーマットを使用して週を表示
format(Sys.Date(), "%Y Week %W")
## [1] "2022 Week 42"

なお、str_glue() を使用する場合は、二重引用符("")の中では一重引用符(' ')を使用することに注意してください。

年月

日付型(Date)の列を月と年に変換するには、 zoo パッケージの as.yearmon() を使うことをおすすめします。これは、日付を適切な順序で yearmon 型に変換する関数です。もし format(column, "%Y %B") を使用した場合、文字型(character)に変換され、値がアルファベット順に並んでしまいます。

以下では、as.yearmon() 関数を使って、date_onset 列から yearmonth 列を新たに作成しています。デフォルト(正しい)の順序で得られた結果を以下のテーブルに示します。

# 新しい列の作成 
test_zoo <- linelist %>% 
     mutate(yearmonth = zoo::as.yearmon(date_onset))

# テーブルの出力
table(test_zoo$yearmon)
## 
## Apr 2014 May 2014 Jun 2014 Jul 2014 Aug 2014 Sep 2014 Oct 2014 Nov 2014 
##        7       64      100      226      528     1070     1112      763 
## Dec 2014 Jan 2015 Feb 2015 Mar 2015 Apr 2015 
##      562      431      306      277      186

一方、format() を使うと、希望の表示形式にはなりますが、正しい順序にはなりません。

# 新しい列の作成
test_format <- linelist %>% 
     mutate(yearmonth = format(date_onset, "%b %Y"))

# テーブルの出力
table(test_format$yearmon)
## 
## Apr 2014 Apr 2015 Aug 2014 Dec 2014 Feb 2015 Jan 2015 Jul 2014 Jun 2014 
##        7      186      528      562      306      431      226      100 
## Mar 2015 May 2014 Nov 2014 Oct 2014 Sep 2014 
##      277       64      763     1112     1070

注意: ggplot() を用いる際、日付の表示のみを調整したい場合は、scale_x_date()date_labels = 引数に strptime フォーマットを使用するだけで十分な場合があります。この場合 "%b %Y" または "%Y %b" を使用できます。詳しくは、ggplot のヒント の章をご覧ください。

zoo には as.yearqtr() という関数もあります。また、ggplot() を用いる際には scale_x_yearmon() を使用できます。

9.9 疫学週

lubridate

日付ごとにデータをグループ化する例は、データのグループ化 の章をご覧ください。以下では、週ごとにデータをグループ化する際の注意点について簡単に説明します。

一般的には、 lubridate パッケージに含まれている関数である floor_date() に引数 unit = "week" を指定して使用することをおすすめします。これは、引数 week_start = で定義される週の「開始日」に日付を丸めるものです。デフォルトの週の開始日は 1(月曜日)ですが、週の任意の日を開始日として指定することができます(例:日曜日は 7)。floor_date() は汎用性があり、unit = を “second”, “minute”, “hour”, “day”, “month”, “year”に設定することで、他の時間単位への切り捨てに使用することができます。

返される値は、日付型(Date)の週の開始日です。日付型(Date)はデータをプロットする際に便利で、ggplot() に認識されやすく、正しい順序で表示されます。

プロットの中で週ごとに日付を調整して表示するだけなら、この章の「日付の表示」のセクションを参照してください。例えば、エピカーブをプロットする際に、必要な strptime の “%” を指定することで、希望の日付表示をフォーマットすることができます。例えば、“%Y-%W” または “%Y-%U” を使用すると、年と週の番号が返されます(それぞれ月曜または日曜の週の開始日を指定)。

週ごとのカウント

count(), group_by(), summarise()によるデータのグループ化については、データのグループ化 の章で詳しく説明しています。以下に簡単な例を示します。

  1. mutate() で新しく “week” という列を作成し、floor_date()unit = "week" を使用する。
  2. 週ごとの行数(症例数)を count() で取得し、日付が欠損しているケースを除外する。
  3. tidyrcomplete() を使用し、行や症例がないものも含めて、すべての週がデータに現れるようにする。デフォルトでは、「新しい」行のカウント値は NA だが、 fill = 引数で 0 にすることができる。 fill = 引数では、名前付きリストを指定する(以下、n はカウント列の名前)。
# 毎週の症例数を集計したデータセットの作成
weekly_counts <- linelist %>% 
  drop_na(date_onset) %>%             # 発症日が欠損のケースを削除
  mutate(weekly_cases = floor_date(   # 発症週の新しい列を作成
    date_onset,
    unit = "week")) %>%            
  count(weekly_cases) %>%           # 週ごとにグループ化し、グループごとの行数をカウント(「n」という列を作成)
  tidyr::complete(                  # 症例が報告されていない週も含めて、すべての週が存在するようにする
    weekly_cases = seq.Date(          # "weekly_cases" という列を完全なシーケンスとして再定義
      from = min(weekly_cases),       # 最小の日付から
      to = max(weekly_cases),         # 最大の日付まで
      by = "week"),                   # 週ごとに
    fill = list(n = 0))             # n という列の NA を 0 で埋める

以下に、上のコマンドを実行して出力されたデータフレームの最初の行を示します。

疫学週 の代替案

なお、lubridate には week()epiweek()isoweek() という関数があり、それぞれ開始日などが微妙に異なります。しかし、一般的には floor_date() があれば十分です。これらの関数の詳細については、コンソールに ?week と入力するか、こちら をご覧ください。

疫学週を設定するために、aweek パッケージの使用を検討してみてください。このパッケージについては、RECON のウェブサイトで詳しく説明されています。このパッケージには、date2week()week2date() という関数があり、week_start = "Monday" で週の開始日を設定することができます。このパッケージを使用すると、最も簡単に “week” フォーマット(例: “2020-W12”)で日付を出力することができます。もう一つの aweek の利点は、date2week() を日付列に適用すると、返される列(週形式)が自動的に因子型(factor)になり、対象期間内のすべての週のレベルが含まれることです(これにより、上述の complete() の余分なステップを回避できます)。ただし、aweek には、日付を月や年などの週以外の時間単位に丸める機能はありません。

時系列の代わりに、“week” フォーマット(“2020 W12”)を表示するのにも適しているのが、パッケージ tsibbleyearweek() です。これは 時系列分析とアウトブレイクの検出 の章で紹介しています。

9.10 日付・タイムゾーンの変換

様々なタイムゾーンでデータが記録された場合、そのデータを一つのタイムゾーンで標準化することが重要になります。これには未だ解決されていない課題があり、多くの場合、データのタイムゾーン要素を手作業でコード化しなければなりません。

R では、日時型(datetime)オブジェクトはタイムゾーン要素を含んでいます。デフォルトでは、すべての日時型(datetime)オブジェクトには、コンピュータのローカルタイムゾーンが適用されます。これは、サマータイムによりタイムゾーンが変更されることが多いため、通常は名前付きのタイムゾーンではなく場所に固有のものです。日付の列が表すイベントは特定の時間に起因するものではないため、時間単位でのタイムシフトを合理的に説明することはできませんので、日付の時間要素がなければタイムゾーンを正確に補正することはできません。

lubridate には日時型(datetime)オブジェクトのタイムゾーンを別のタイムゾーンに変更するためのヘルパー関数がいくつかあります。tz データベースのタイムゾーンを日時型(datetime)オブジェクトに適用することで、タイムゾーンの設定が行えます。データを使用している場所が以下の一覧にない場合は、近隣の大都市のタイムゾーンが利用可能です。

https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

# 現在の時刻を列に割り当て
time_now <- Sys.time()
time_now
## [1] "2022-10-18 11:54:31 UTC"
# with_tz()を使用して、時刻を変更しながら、新しいタイムゾーンをカラムに割り当て
time_london_real <- with_tz(time_now, "Europe/London")

# force_tz()を使って、時刻を維持したまま、新しいタイムゾーンをカラムに割り当て
time_london_local <- force_tz(time_now, "Europe/London")


# このコードを実行したコンピュータがロンドン時間に設定されていない場合は
# 時間の差が発生する
# (コンピュータのタイムゾーンとロンドンのタイムゾーンとの差の時間数)
time_london_real - time_london_local
## Time difference of 1 hours

この作業は抽象的に見えるかもしれません。複数のタイムゾーンにまたがったデータを扱わなければ、このような作業は不要でしょう。

9.11 前後の値の計算

lead()lag()dplyr パッケージの関数で、ベクトル(通常は数値や日付のベクトル)の中から前の値(遅れた値)や後の値(先行する値)を見つけるのに役立ちます。これは、時間単位での変化や差を計算するときに便利です。

例えば、現在の週と前の週の症例数の差を計算したいとします。元のデータは、以下のように週ごとのカウントになっています。

lag()lead() を使用する際には、データフレーム内の行の順序が非常に重要です。日付や数字が昇順か降順かに注意してください

まず、前週(lagged)の値を含む新しい列を作成します。

  • n = で前後のユニット数(非負の整数)を指定する。
  • 存在しない行(前の値がない最初の行など)に置かれる値を定義するには、default = を使用する。デフォルトは NA
  • 参照する列が順序付けられていない場合は、order_by = TRUE を使用する。
counts <- counts %>% 
  mutate(cases_prev_wk = lag(cases_wk, n = 1))

次に、2 つの症例の列の差分となる新しい列を作成します。

counts <- counts %>% 
  mutate(cases_prev_wk = lag(cases_wk, n = 1),
         case_diff = cases_wk - cases_prev_wk)

lead()lag() については、こちら をご覧いただくか、コンソールで ?lag と入力してください。

9.12 参考資料

lubridate tidyverse page
lubridate RStudio cheatsheet
R for Data Science page on dates and times
Online tutorial
Date formats