-
Notifications
You must be signed in to change notification settings - Fork 0
/
ch4.qmd
1088 lines (803 loc) · 60 KB
/
ch4.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# データ型に応じた処理
これまでは**tidyverse**で利用可能な、データ操作に特化したパッケージの働きについて見てきたが、この章では前半に文字列、因子データの処理として**stringr**パッケージおよび**forcats**を導入する。また後半では日付・時間のデータを扱うための**lubridate**パッケージを紹介する。これらのパッケージはすべて**tidyverse**に含まれており、`library(tidyverse)`で利用可能になる。パッケージの関数は単一で利用しても便利であるが、パイプ演算子や**dplyr**、**purrr**といったパッケージと組み合わせることで、より利用範囲を広げられるだろう。
## 文字列処理 {#string}
データ分析において文字列は、性別や曜日などの決まった項目が用いられるものからユーザ名のように利用可能な文字や長さに制限があるもの、アンケートの回答結果など様々だ。文字列の操作には結合や分割、検索、置換などがある。まずはRにおける文字列データの基本を覚えよう。
### Rにおける文字列の扱い
Rでは文字列を場合、引用符により文字列を囲む必要がある。引用符は一重引用符 (シングルクオート `'`)でも二重引用符 (ダブルクオート `"`)でも構わないが、文字列を囲む際はその種類を揃えておかなければならない。
```{r}
# 引用符によって囲んだ文字が文字データとして扱われる
"こんにちは"
class('引用符で囲む')
```
引用符を揃えない限り、Rコンソールは文字列の入力を受け付ける状態となる。始まりと終わりと引用符の種類が揃っていれば良いと先に述べたが、極力ドキュメントやプロジェクト中で引用符の種類は変更しないことが望ましい。
```
"引用符の種類は統一しておく'
# +
```
では文字列の中で引用符を利用するにはどうするか。これは文字に含ませる引用符と異なる引用符を囲み文字に採用するか、バックスラッシュ記号 ("\\") で引用符をエスケープすることで可能となる。
```{r}
# cat()は与えられた文字列をコンソールに出力する標準出力のための関数
cat("引用符 ' を使う")
cat('引用符 " を使う')
# バックスラッシュによるエスケープも可能
cat('引用符 \' を使う')
```
文章を区切るための改行は、Rでは`\n`で表現する。`\n`で一行分の改行となる。
```{r}
cat("こんにちは。\n今日は天気が良いですね。")
```
### stringrパッケージを使った文字列操作
**tidyverse**には**stringr**パッケージが文字列の処理に特化したパッケージとして用意される。本書でもこのパッケージを利用した例を紹介するが、**stringr**の関数が提供する正規表現の機能は、名称の類似した**stringi**が元となっている\footnote{2015年にリリースされたstringr 1.0.0より}。興味のある読者は**stringi**の関数やドキュメントを参照すると良い。
**stringr**パッケージの関数による文字列操作はRで標準利用可能な関数としても提供されているものが多いが、標準で利用できる関数はパイプ処理と組み合わせて利用することを想定していない。また**stringr**では**stringi**パッケージがサポートするIUC (International Components for Unicode)ライブラリの機能を利用できる。ICUライブラリはユニコードに関するさまざまな処理や操作に優れている。また、いくつかのRの標準関数が因子型のデータを入力値として利用できないのに対して、**stringr**は文字列型と同様に処理するという違いもある。
<!-- stringr と stringiの関数の対応表 (付録?) -->
ここからは**stringr**パッケージを使った文字列の操作方法について紹介していこう。また、一部で**stringi**パッケージの関数を参照することもあるが、**stringr**パッケージのインストールが済んでいれば追加でインストールする必要はない。**stringr**パッケージのほとんどの関数の多くは`str_*()`で実行される。`str_`の接頭辞のあとは関数が行う処理の内容を示している。
```{r load_packages}
library(stringr)
```
#### 結合
`str_c()`は、引数に与えた複数の文字列を一つの文字列に結合する関数である。結合の際に文字列の間に任意の文字を指定可能であり、これは*sep*引数で指定する。既定値は""(空白文字列)となっている。引数内で複数の要素をもつベクトルが与えられた時、結合はベクトルの各要素に対して行われる。
```{r}
# 引数に与えられた文字を結合する
str_c("こんにちは", "今日の天気は", "晴れです")
# sep引数で文字の区切りとなる位置に指定した文字を挿入する
str_c("こんにちは", "今日の天気は", "晴れです", sep = "_")
# 長さが異なるベクトルが与えられた時、要素は再利用される
# 返り値は要素の数が最も多いベクトルに合わせて出力される
str_c("こんにちは今日の天気は",
c("晴れ", "曇り", "雨"),
"です")
```
*collapse*引数を利用することで、返り値が複数の要素になる出力においても、単一のベクトルとして結合が行われる。*collapse*に指定した文字は、入力に与えられた要素と要素をつなぐ文字列として利用される。
```{r}
# 3つの長さのベクトルをcollapse引数で与えた文字列によって単一のベクトルに結合する
str_c("こんにちは今日の天気は",
c("晴れ", "曇り", "雨"),
"です",
collapse = "。")
```
同じく、`str_flatten()`も与えられた文字列の要素を結合して一つの文字列として返却する。
```{r}
str_flatten(c("あ", "い", "う", "え", "お"))
str_flatten(c("あ", "い", "う", "え", "お"), collapse = "_")
```
#### 抽出
文字列から一部の文字を取り出すには`str_sub()`および`str_subset()`を使う。`str_sub()`は引数*start*と*end*それぞれで抽出したい文字の位置を指定する。この際、負値を与えることも可能であり、例えば*start*に-2を与えると、文字列の末尾から2文字分遡った位置が起点となる。
```{r}
str_sub(string = "こんにちは", start = 3, end = 5)
# 負値により、対象が文字の末尾になる
str_sub(string = "こんにちは", start = -2)
```
文字の位置ではなく、対象の文字列、パターンが含まれるものを取り出すには`str_subset()`を使う。また、値そのものではなく、該当箇所の位置に興味がある場合は`str_which()`を用いると良い。
```{r}
# 「ん」を含んだ文字列の要素を返却する
str_subset(c("おはよう", "こんにちは", "こんばんは"),
pattern = "ん")
# 2, 3番目の要素に「ん」が含まれる
str_which(c("おはよう", "こんにちは", "こんばんは"),
pattern = "ん")
```
#### 長さを調べる、回数を数える
「こんにちは」は長さが5の文字列であるが**stringr**では文字の長さを数えるのに`str_length()`を利用する。
```{r}
str_length(string = "こんにちは")
str_length(c("つくば", "Tsukuba", "筑波"))
```
入力に欠損が与えられた場合にはNAが返される。これはRに標準で利用可能な関数として用意される`nchar()`と同じ挙動であるが、`str_length()`は因子型のデータを与えた場合にも文字列のカウントを実行する(`nchar()`は因子型のデータの入力を受け付けない)。
```{r}
str_length(NA)
animals <-
c("cat", "dog", "mouse", "boar") %>%
factor()
str_length(animals)
```
文字列中に含まれる単語や一文の数を数える処理は`str_count()`でも実行できる。この関数は引数*pattern*で指定した文字列が含まれる回数を数えるもので、次の処理ではベクトルの各要素に含まれる「ん」の文字数を数えている。
```{r}
str_count(c("こんにちは", "こんばんは", "こん"),
pattern = "ん")
```
#### パターンマッチ
対象の文字列の中の一部、あるいは特定の要素に対してのみ処理を実行する、という時にはパターンマッチを利用した処理が有効である。例えばパターンマッチの方法には、先に`str_count()`の例で見たように特定の文字列を与えることもできるが、**stringr**ではより柔軟に文字列のマッチングを行える仕組みが備わっている。
**stringr**パッケージで利用可能な関数の引数*pattern*は、次の方法によるパターンマッチが可能である(既定では`regex()`を用いて指定する正規表現が適用される)。これらの方法と関連する話題について詳細を見ていくために、まずは4つの文字を含んだ文字列ベクトルを用意しよう。続いて`str_detect()`を用いながら、引数*pattern*に与える処理を変えながら、それぞれの挙動を確認する。
`fixed()`:
与えられた文字を直接評価するパターンマッチ
`regex()`:
正規表現によるパターンマッチ。ICUの正規表現規則に従う
`coll()`:
ロケールを考慮したパターンマッチ
`boundary()`:
文章の境界、すなわち文字 character、単語 word、改行 line_breake、段落 sentenceで指定するパターンマッチ
`str_detect()`は指定したパターンが対象の文字列に含まれるかを判定する関数だ。返り値は論理値である。パターンと一致すれば*TRUE*となる。最初の例では、"\^a"を*pattern*に指定した。2番目は`fixed()`の内部で同じ文字列を指定したが、結果は異なっている。`fixed()`は、*pattern*に与えられた文字列を直接パターンマッチに利用するためである。"\^a"で使っている"^"は、後続のパターンが先頭文字を示す記号として評価されるもので、対象となる**strings**の中にはその文字は含まれない。
*pattern*に指定可能な関数には、`boundary()`を除いて*ignore_case*引数が用意されている。これはアルファベットの大文字と小文字を区別しないためのオプションで、通常は*FALSE*が与えられる(大文字と小文字を厳密に区別する)。3番目の例では`regex()`を使っているいるが、*igreno_case*を*TRUE*とし、先頭が"a"となる大文字・小文字がある場合に*TRUE*を返すようになっている。
```{r}
# \u0061 はaのユニコードエスケープ
strings <-
c("a", "\u0061", "A", "あ")
# 既定値はregex()の指定と等しい
str_detect(string = strings,
pattern = "a")
# fixed()を使用
str_detect(string = strings,
pattern = fixed(pattern = "^a"))
# regex()を明示して使用。先頭がaまたはAで始まる文字にマッチ
str_detect(string = strings,
pattern = regex(pattern = "^a",
ignore_case = TRUE))
```
また`regex()`は他に*multiline*、*comments*、*dotall*引数を備えている。これらはいずれも論理値を指定する。既定では*FALSE*が与えられている。*multilne*が与えられた要素に改行("\\n")があった時に、改行ごとにパターンマッチを評価するかを、*comments*は*TRUE*の時に、要素に含まれる、先頭文字の"#"記号および空白文字列を無視する、*dotall*は"."を文字として評価しないことをそれぞれ意味している。これらのオプションは`regex()`は、"\^"や"$"、"."といった記号が次に述べるように、正規表現の記号と区別するために機能する。
```{r}
str_detect("今日の天気は晴れです。\n気温は26度を超えます。",
regex("です。$", multiline = TRUE))
str_detect("# 今日の天気は晴れです。",
regex("です。$", comments = TRUE))
str_extract("# 今日の天気は晴れです\n",
regex("晴れです.", dotall = FALSE))
str_extract("# 今日の天気は晴れです\n",
regex("晴れです.", dotall = TRUE))
```
続いて、`coll()`を*pattern*に指定した場合は次のような結果を返す。`coll()`は、ロケール(3章参照)を考慮したパターンマッチを適用する関数で、例えば、大文字のIはユニコード表記では"\\u49"となるがトルコ語などはIと区別してİを利用する。これはユニコード表記では"\\u0130"である。どちらも文字としてはIを示すため、*locale*にトルコの言語ロケールである"tr"を指定するとマッチングする。また、ここで指定可能なロケール一覧は`stringi::stri_locale_list()`によって確認できる。
```{r}
i <-
c("I", "\u0130")
str_extract(string = i,
pattern = coll(pattern = "I"))
str_extract(string = i,
pattern = coll(pattern = "i", ignore_case = TRUE, locale = "tr"))
```
最後に`boundary()`の例であるが、これは文字の分割を行う`str_split()`の節で紹介する。
##### 正規表現
正規表現は文字列のパターンを表現する方法の一つである。文字列の中から特定の文字を検出したり、検出した文字を置換する、といった処理が正規表現を通して実行できる。正規表現は、Rの標準機能にも備わっているが、**stringr**パッケージが実装するICUのライブラリはより自由度の高い文字列パターンを扱うことができる。実際にこの章で紹介してきた関数で引数*pattern*があるものでは、同様の指定と処理が可能である。正規表現の詳細については奥が深く、すべてを理解することは困難であるが、ここで述べるような基本的な利用方法や、ある程度の内容について理解しておくだけで充分な価値がある。
3つの要素を文字ベクトルから引数*pattern*に指定した文字「ほくそ」が含まれているかを判定する処理は以下のようになる。
<!-- \footnote{詳細は?stringi::`stringi-search-regex`で表示されるドキュメントを参照}。 -->
```{r}
hoxom <-
c("ほくそえむ", "ほくそうむ", "ほくそうり")
str_detect(hoxom, "ほくそ")
```
ベクトル*hoxom*に格納したすべての文字列は「ほくそ」を含んでいるため、この結果はすべて真となる。では次の例はどうだろうか。
```{r}
str_detect(string = hoxom, pattern = "む$")
```
今度は3つ目の「ほくそうり」で*FALSE*を返すようになった。上記のコードで与えたパターンは「む」で終わる、である。記号`$`は文字列の終わりを示している。この他にも正規表現で利用可能な記号はいくつかの種類があり、**メタ文字**と呼ばれる正規表現の中で利用できる修飾子である。メタ文字を利用したパターンマッチの処理の例をいくつか見てみよう。
```{r}
# xの要素はすべて「ほくそ」で始まるため、任意の一文字を示す . を使うとマッチしなくなる
str_detect(string = hoxom, pattern = ".ほくそ")
# 先頭および末尾の文字列を、^と$で指定したパターン
# 「ほくそ」と「む」の中には別の文字列が含まれていても良いとする
str_detect(string = hoxom, pattern = "^ほくそ.+む$")
```
```{r}
fruits <-
c("バナナ", "リンゴ", "パイナップル")
# 「ナ」が含まれる文字列にTRUEを返す
str_detect(fruits, "ナ")
# {2}は「ナ」の2回繰り返しを示す
# バナナはナが連続して出現するが、パイナップルはナが1回しか出現しない
str_detect(fruits, "ナ{2}")
# 3回以上4回未満の「ナ」の出現はいずれの要素にも含まれない
str_detect(fruits, "ナ{3,4}")
```
```{r}
prefs <-
c("神奈川県", "東京都", "沖縄県", "岡山県", "富山県")
# 「県」で終わり、山または川を含んだ文字列にマッチ
str_detect(prefs, "(山|川)県$")
```
<!-- 表: Rがサポートする修飾子(メタ文字) -->
| 修飾子 | 意味 | 指定の種類 |
|------------|:-----|:-----------|
| ^ | 以後の文字が先頭にある | 位置 |
| $ | 以前の文字が後尾にある | 位置 |
| . | 任意の一文字 | 対象 |
| | | 複数のパターンに区切る | 対象 |
| (), [], {} | パターンをグループ化する | 対象 |
| \* | 直前パターンをn回繰り返し(n >= 0) | 繰り返し |
| ? | 直前パターンをn回繰り返し(n = 0, 1) | 繰り返し |
| + | 直前パターンをn回以上の繰り返し | 繰り返し |
| {n} | 直前パターンをn回繰り返し | 繰り返し |
| {n, } | 直前パターンをn回以上の繰り返し | 繰り返し |
| {n, m} | 直前のパターンをn回以上m回以下繰り返し | 繰り返し |
指定したパターンが文字列中のどの位置でマッチしているかを確認する方法として、**stringr**では`str_view()`あるいは`str_view_all()`が用意されている。この関数は`str_detect()`などの関数同様、対象の文字列とパターンマッチに用いる文字列を指定するが、実行するとウェブブラウザまたはRStudioを利用している場合はViewerパネル中に、マッチする箇所を表示する画面が出現する。グレーでハイライトされている箇所が引数*pattern*で指定した文字列とマッチする箇所を示している。引数*match*は*string*に与えられた文字列中にマッチする要素だけ、あるいはマッチしない要素だけを表示するかを選択するオプションでり、論理値で指定する。既定では*NA*が与えられ、いずれの要素も表示する。
```{r, eval = FALSE, echo = TRUE}
str_view(string = hoxom, pattern = "ほくそ")
str_view(string = c("ほくそ", "もなぎ"),
pattern = "ほくそ", match = FALSE)
```
![str_view()によるマッチ箇所の確認](images/fig-ch5-str-view.png)
##### 文字クラス・POSIX文字クラス
対象の文字が特定の文字列の組み合わせで生成されている場合には、文字クラスあるいはPOSIX文字クラスを利用したパターンマッチが可能になる。文字クラスでは、ブラケット (`[]`)で挟んだ範囲の文字をパターンとみなし、更に連続する文字列の場合、例えばAからZまでのアルファベッドを指定する際にABC..Zとするのではなく、`A-Z`と対象の文字列の範囲を`-`で文字をつなぐことで複数の文字をパターンに含めることができる。
<!-- 表: Rがサポートする文字クラス -->
| 文字クラス | 対象 |
|:-----------|:-----|
| [0-9] | アラビア数字 |
| [a-z] | 小文字アルファベット |
| [A-Z] | 大文字アルファベット |
| [ぁ-ん] | ひらがな |
| [ァ-ヶ] | カタカナ |
| [一-龠] | 漢字 (すべての漢字に対応できるわけではない) |
| [\\x01-\\x7E] | 1バイト文字 |
```{r}
strings <-
c("ひらがな", "カタカナ", "漢字", "ABC", "abc", "123", "a2c4e6")
# 大文字のA, B, Cのいずれかを含んだパターン
str_detect(strings, "[A-C]")
# ひらがなにマッチするパターン
str_detect(strings, "[ぁ-ん]")
# 複数の文字クラスやメタ文字を扱うこともできる
# アルファベットのAからZまで(大文字、小文字)を含んだパターンの検出
# 「ABC」, 「abc」, 「a2c4e6」にマッチする
str_detect(strings, "[A-Za-z]")
str_detect(strings, regex("[A-Z]", ignore_case = TRUE))
# 先頭および末尾が数値で、長さが3の文字とマッチするパターン
# 「123」に該当
str_detect(strings, "^[0-9]{3}$")
# ひらがな、カタカナ、漢字の一部を含んだパターンを検出
# 「ひらがな」, 「カタカナ」, 「漢字」にマッチ
str_detect(strings, "[ぁ-んァ-ヶ一-龠]")
```
POSIX文字クラスは一致するパターンを特定の種類にまとめ、特殊な表記を行うことで複数の文字を一度に対象パターンとして取り扱う。POSIX文字クラスは文字クラス同様、ブラケットを利用した記述を利用するが、加えてコロン (`:`)によってPOSIX文字クラス名を囲むという特徴がある。Rで利用可能なPOSIX文字クラスについて表XXに示した。
<!-- 表: RがサポートするPOSIX文字クラス -->
| POSIX文字クラス | 対象 |
|:----------------|:-----|
| [:alnum:] | アルファベットと数値([:alpha:] + [:digit:]) |
| [:alpha:] | 大小文字アルファベット([:upper:] + [:lower:]) |
| [:upper:] | 大文字アルファベット |
| [:lower:] | 小文字アルファベット |
| [:digit:] | 数値 |
| [:blank:] | 空白文字、スペースとタブ |
| [:cntrl:] | 制御文字 |
| [:graph:] | 空白以外の文字 ([:alnum:] + [:punct:]) |
| [:print:] | 印字可能な文字([:graph:] + スペース) |
| [:punct:] | 補助符号を含めた句読点(! " # $ % & ' ( ) * + , - . /) |
| [:space:] | すべての空白文字 |
| [:xdigit:] | 16進数で認められている文字(0-9a-fA-F) |
```{r}
strings <-
c("alphabet", "123456", "alnum789", "123 456")
str_extract(string = strings, pattern = "[[:alpha:]]")
str_extract(strings, "[[:digit:]]")
str_extract(strings, "[[:space:]]")
```
ここで用いた`str_extract()`は、パターンにマッチした箇所を文字列で返却する。もし対象の文字列に含まれるパターンが指定されない場合、欠損が与えられる。`str_extract()`ではメタ文字による繰り返しを指定しない場合は最初に該当した箇所の文字を返すが、これに対して、対象の文字列中ですべての該当箇所を返すようにするには`str_extract_all()`を利用すると良い。この関数の返り値はリストであるが、引数*simplify*を*TRUE*とすることで各要素でマッチした箇所を含んだ行列になる。
```{r}
# 対象文字列でマッチするすべての文字列をリストで返す
str_extract_all(string = strings, pattern = "[[:alpha:]]")
# 各要素を行とし、マッチした箇所を含んだ行列として返却
str_extract_all(strings, "[[:alpha:]]", simplify = TRUE)
```
複数の要素を与えた際、どの要素に対してマッチが行われているかを判断するために、`str_which()`を使う方法もある。
##### 文字ロケールの利用
[WIP]
<!-- stringi::stri_locale_info() -->
#### 文字列の削除と置換
文字列の中には、思わぬ空白文字列や改行が含まれ、処理の邪魔となることがある。`str_trim()`や`str_squish()`はこれらの余分な空白文字列や余分な改行を取り除く処理を実行する。次の文字列から、余分な改行や空白を取り除いてみよう。この文字列には、前後および途中で改行が与えられており、「でも」の後に空白文字列が与えられている。
```{r}
# 前後および文章の中に空白を含んだ文字列ベクトルを用意する
(x <- c("\n こんにちは。今日は天気が良いですね。\nでも 明日は雨が降るらしいです \n\n\n"))
```
`str_trim()`は引数*side*の指定により、除去を行う方向を指定する。既定値では文字列の両側を対象にする("both")が、他に左右の指定("left"および"right")が可能である。
```{r}
# 文字列に含まれる空白文字の除去。既定値で両側の空白が対象になる
str_trim(x)
str_trim(x, side = "left")
```
さて、`str_trim()`によりいくつかの文字が取り除かれたが、依然として文章中に必要以上の空白が残っている。これは`str_squish()`で、単一の空白文字列へと置換可能できる。
```{r}
str_squish(x)
```
<!-- ユニコード正規化の話は別の場所に移動。ただしコラムとして独立させるには勿体無い -->
特定の文字列や文字列の一部を任意の値に変更する処理を実行するために`str_replace()`が利用できる。この関数には引数*pattern*と*replace*があり、対象のパターン、置換後の文字を指定する。
```{r}
str_replace("こんばんは",
pattern = "ばんは",
replacement = "にちは")
```
`str_replace()`では、パターンに一致した箇所の置換を、最初に一致した箇所のみ対象にするが、すべての一致する箇所で置換を適用するには`str_replace_all()`を使う。
```{r}
# 「こんばんは」中の「ん」を空白文字列に置換
# str_replace()では、最初の「ん」しか変更されない
str_replace("こんばんは", "ん", " ")
# str_replace_all()を用いることですべての箇所が置換の対象になる
str_replace_all("こんばんは", "ん", " ")
```
また`str_replace_all()`では、複数の置換パターンを与えることも可能だ。これは引数*pattern*に`c(対象文字列 = 置換後の文字列)`の形で指定する。
```{r}
# 複数のパターンの置換を同時に実行する
x <-
c("こんばんは", "こんにちは 今日は寒いですね")
str_replace_all(x,
c("こんばんは" = "今晩は", "寒い" = "暑い"))
```
次の例は、*pattern*引数に指定した住所文字列にマッチするパターンとして「東京都」「道」「府」「県」の4種を指定している。複数のパターンに対してマッチングを行うにはこのようにして`|`記号を用いて文字列を区切る。また京都府の場合には、2文字目の「京"都"府」にマッチしてしまうので、「都」を含んだ文字列でマッチングさせるのは「東京都」となるようにした。
```{r}
address <-
c("東京都渋谷区桜ヶ丘",
"岡山県岡山市北区清心町",
"茨城県つくば市小野川",
"京都府舞鶴市字浜")
str_replace(string = address,
pattern = "(東京都|道|府|県).+",
replacement = "\\1")
```
正規表現では、パターンに含める文字列を括弧で囲むことで、マッチさせた文字列を利用できるようになる。これは**後方参照**と呼ばれる。マッチした文字列は、置換後の値を指定する引数*replacement*で`\\1`として呼び出している(`\`を繰り返すのはエスケープのため)。
マッチした箇所の文字列だけを除外するには、`str_remove()`を使う方法がある。これは`str_replace(string, pattern, replacement = "")`と同じ働きをもつ。この関数も対象文字列中でマッチする全箇所を対象にする`str_replace_all()`がある。
```{r}
str_remove("文字列の「この部分」を削除する", "この部分")
# 最初にマッチした箇所を除外する
str_remove("Hello world", "o")
# 全てのマッチ箇所を除外の対象にする
str_remove_all("Hello world", "o")
```
このほか、文字列置換の特殊な用途として、欠損 *NA*を文字列に変換する`str_replace_na()`が用意されている。
```{r}
str_replace_na(c(NA_character_, "abc", "こんにちは"))
```
#### 分割
`str_split()`および`str_split_fixed()`は、指定したパターンにマッチした箇所を起点に、対象の文字列を分割する関数である。ここで、分割に指定したパターンは、`boundary()`による文字の境界以外は返り値に含まれないという特徴がある。通常、対象の文字列でマッチする箇所があるたびに分割が行われるが、これは引数*n*に分割する数値を指定して制御できる(既定では*Inf*が指定される)。またこのオプションは`str_split()`ではリストの要素数の指定となるが、`str_split_fixed()`では行列の列数となり、ユーザの指定が必要なものとなる。なお`str_split()`の返り値はリストであるが、*simplify*引数で*TRUE*を指定すると`str_split_fixed()`と同じく行列が返されるようになる。後続の処理の用途に応じて使い分けると良いだろう。
```{r}
souseki_text <-
c("吾輩は猫である。名前はまだない。\nどこで生れたかとんと見当けんとうがつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。")
```
```{r}
# 「である。」および「つかぬ。」が出現する位置で要素を分割する
str_split(string = souseki_text, pattern = "(である|つかぬ)。")
# 引数nで返り値の要素数を制御
# n以上の分割は行われない
str_split(souseki_text,
"(である|つかぬ)。",
n = 2)
# str_split_fixed()の返り値は行列で、引数nにより列数を指定する
str_split_fixed(string = souseki_text,
pattern = "(である|つかぬ)。",
n = 2)
```
*pattern*の指定に`boundary()`を利用する場合、入力に用いた文字は残り、分割だけが行われる。
```{r}
str_split(souseki_text, boundary("character"))
# マルチバイト文字を含んだ改行はうまくいかないことがある
str_split(souseki_text, boundary("line_break"))
str_split("blah\nblah\nblah", boundary("line_break"))
str_split(souseki_text, boundary("word"))
str_split(souseki_text, boundary("sentence"))
```
#### 要素の並び替え
文字列要素の順番や並び替えに関する関数は2つ存在する。`str_order()`
```{r}
str_order(c("あ", "え", "い", "う", "え", "お", "あ", "お"),
locale = "ja_JP")
str_sort(c("あ", "え", "い", "う", "え", "お", "あ", "お"))
stringi::stri_order(c("い", "あ", "う", "あ"))
str_order(c("a", "b", "a"))
str_sort(letters)
```
#### エンコード変換
日本語などを含んだ文字列が正常に表示できていない状態、文字化けを起こしている場合、`str_conv()`を使い、エンコードし直すことで問題に対応することができる。新たに指定するエンコードは引数*encoding*で、エンコード名を文字列で与えるが、その一覧は`stringi::stri_enc_list()`で確認できる。この中で日本語に関するエンコードとしては"EUC-JP"、"ISO-2022-JP"、"cp932"、"UTF-8"などがある。
```{r}
x <- "\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8"
# str_conv()では元のエンコードを指定するだけで自動的に現在のエンコード形式に変更する
str_conv(x, encoding = "cp932")
```
#### 文字列フォーマット
指定した書式(フォーマット)に基づき、与えられた値を文字列に変換する文字列フォーマットは、`str_interp()`ならびに`str_glue()`により提供される。これらの関数は、Rオブジェクトの値を利用することも可能である。やや指定方法に癖があるが、慣れれば、柔軟に文字列の生成が行えるので大変便利な関数である。
`str_interp()`、`str_glue()`にはいくつかの利用方法がある。まず、Rオブジェクトの値を文字列中に利用するには、次のようにドル記号("$")に次いでブラケット("\{\}")で囲んだ中にRオブジェクトの変数名を与える。フォーマットを指定する場合、ドル記号の後、"\{"ブラケットの前に"["ブラケットの囲み文字でフォーマット指定を行う。指定可能なフォーマットはRで標準利用できる`sprintf()`と同じく、代表的なものを表XXに整理している。
```{r}
my_name <- "真也"
age_year <- 1989
age_jp_year <- 1
# 3つのオブジェクトを文字列の中で利用する。
# age_jp_year, age_yearの2箇所でフォーマットを指定している(ともに整数)
str_interp("私の名前は${my_name}です。
平成$[02d]{age_jp_year}($[4d]{age_year})年の生まれです。")
```
関数内で参照するRオブジェクトの評価は、文字列の出力時に行われるため、次のように修正を加えることもできる。
```{r}
str_interp("$[4d]{age_year - 4}年の生まれです。")
```
関数内部でオブジェクトを生成し、それを利用することも可能である。これには`list()`を使い、変数名と値の組み合わせを指定する。
```{r}
str_interp("$[4d]{age_year}年の生まれです。",
env = list(age_year = 2000))
```
`str_glue()`は**glue**パッケージが提供する機能を実装したもので、`glue::glue()`、`glue::glue_data()`と同じ引数オプションが利用できる。
```{r}
str_glue(
"{when}の時刻は {datetime}\n",
"時刻を書式化すると{format(datetime, '%Y年%m月%d日 %X')}\n",
"日付は {format(datetime, '%Y年%m月%d日')} です\n",
"時刻は {format(datetime, '%X')} です",
when = "ただいま",
datetime = Sys.time()
)
df_jyunishi <-
data.frame(
eto = c("子", "丑", "寅", "卯", "辰", "巳", "午",
"未", "申", "酉", "戌", "亥"),
stringsAsFactors = FALSE)
# データフレームの変数を参照する
# 複数の要素が与えられた時は、与えられた数の出力を行う
df_jyunishi %>%
str_glue_data("{2008 + 0:11}年の干支は{eto}です")
```
<!-- 表番号 2015年12月25日 14時1分30秒 を例にした -->
| 表記 | 表現 | 表記の例 |
|------|--------------|----------------|
| %a | 曜日名の略称 | Fri |
| %A | 完全な曜日名 | Friday |
| %b | 月名の略称 | Dec |
| %B | 完全な月名 | December |
| %c | 日付と時間 ("%a %b %e %H:%M:%S %Y") | Fri Dec 25 14:00:00 2015 |
| %y | 西暦の下2桁 | 15 |
| %Y | 西暦 | 2015 |
| %m | 月 | 12 |
| %d | 日(1月の範囲) | 25 |
| %j | 日(1年の範囲) | 359 |
| %H | 時間(24時間) | 14 |
| %I | 時間(12時間) | 02 |
| %M | 分 | 01 |
| %p | 午前と午後の区分 | PM |
| %S | 秒 | 30 |
| %w | 曜日\footnote{日曜日を0とする} | 5 |
| %x | 年月日 ("%m/%d/%y") | 12/25/2015 |
| %X | 時刻 ("%H:%M:%S") | 14:01:30 |
#### 桁揃えと丸め込み
文字数を揃える際には、`str_pad()`、`str_trunc()`の2つの関数が役に立つ。これらはそれぞれ、桁揃え、丸め込みを行う関数で、処理を適用する方向を前後、両側あるいは中央のいずれかで指定できる。
例えば、1から20までの数値ですべての値を2桁に揃えるという時には、次のように*width*で桁数、*pad*に補間する任意の文字列、"0"を与えた`str_pad()`を実行する。
```{r}
str_pad(1:20, width = 2, pad = "0")
```
`str_pad()`では、補間する文字列を挿入する位置を指定する*side*引数を除いた引数の値はベクトル化される。すなわち次のように与えられた値と処理の組み合わせを返却する。
```{r}
str_pad("a", width = c(5, 3, 4), pad = "_", side = "right")
str_pad("a", width = 2, pad = c("_", "-"), side = "right")
```
`str_trunc()`は*string*の文字列の長さを*width*の数値になるよう調節する機能をもつ。既定では"..."が省略時の文字列として使われるが、これは*ellipsis*によりユーザが変更可能である。
```{r}
str_trunc("aaaaaa", 5)
# side引数で省略位置の指定を行う
str_trunc("aaaaaa", 5, side = "center")
# ellipsis引数で省略記号を変更する
str_trunc("aaaaaa", 5, ellipsis = "(略)")
```
### forcatsパッケージ {#forcats}
順序や項目に意味のある因子型データ (factor, カテゴリデータ)を扱うのに便利なパッケージとして**forcats**を取り上げる。因子型のデータは、3章において`data.frame()`で文字列を変数に与えた場合に変換される見たように、Rの多くの標準関数、統計処理(`glm()`などの関数で指定する*formula*に文字列型データを与えた場合、因子型へ自動変換が行われる)で利用されている。
一方で、どのように因子型として扱われているのか、水準の並びがどうなっているのかを把握せずに処理を進めていくと予想外の結果を招く危険もある。そのためtidyverseのパッケージおよびtibble形式のデータフレームは文字列の因子型への変換を自動的には実行しない仕組みになっている。因子型への変換は、必要に応じて実行すれば良い。
**forcats**には、ユーザの使い勝手を向上させた、因子型の操作関数が豊富に含まれている。その使い方を見ていくことにしよう。**forcats**の関数の多くは第一引数に対象とする文字列または因子型のベクトルを指定する。また関数名には`fct_`という接頭辞が与えられている。
```{r}
library(forcats)
```
まず**forcats**の因子型変換の基本であるが、Rで標準利用可能な因子型への変換を行う関数`as.factor()`と異なる挙動として、要素が出現した順に水準を与える。これはロケールの設定により結果が異なる可能性のある挙動を制御するのに有効な仕様である。
```{r}
# 標準関数ではロケールの設定を反映した並び替えが行われる
as.factor(c("A", "C", "B"))
as.factor(c("あ", "う", "い", "お"))
# forcats::as_factor() では要素の出現した順番に水準が与えられる
as_factor(c("A", "C", "B"))
# sort()を適用して水準を定義する処理
c("あ", "う", "い", "お") %>%
sort() %>%
as_factor()
```
ここからは架空SNSデータを用いて**forcats**パッケージの因子操作関数の解説を行う。データの読み込みは3章で説明した**readr**パッケージを使った方法と同じである。**df_sns**にはnationalityという列があり、この列はSNSへの投稿を行ったユーザの国籍をアルファベット2文字で示した国コードである\footnote{ISO 3166-1 alpha-2に準拠した}。
```{r, results = "hide"}
df_sns <-
readr::read_csv("data/sns.csv")
df_sns$nationality[1:5]
```
#### 水準のカウント
`fct_count()`は指定したベクトルの頻度をカウントする`dplyr::count()`関数と類似の機能をもつ関数である。`count()`との違いは、ベクトルで頻度を数える値を指定すること、集計後の水準が因子型になること、出力結果はデータフレームであるが、変数名が対象のベクトルを指す*f*と頻度の*n*からなることである。しかし水準の並びは規定では、頻度ではなく文字の並びとなるので注意が必要である。これは引数*sort*で*TRUE*を指定することでデータフレームの並びが頻度の降順になる。
```{r}
df_count <-
fct_count(df_sns$nationality)
# ベクトルで与えた変数は因子として扱われる
df_count$f %>% levels()
# 出力されるデータフレームの並びを頻度の降順にする
fct_count(df_sns$nationality, sort = TRUE) %>%
head()
```
#### 水準の並びかえ
Rの因子型データへの変換は、ユーザが順位づけを指定しない場合、文字列の並びを優先して並びをつける。これに対して水準の並びを入れ替える関数`fct_inorder()`と`fct_infreq()`はそれぞれ、値の出現した順、頻度の多い順に水準の順位づけを行う。
```{r}
# アルファベットの並びで順序が与えられる
df_sns$nationality %>%
factor() %>%
levels()
# fct_inorder()... それぞれの水準の並びの違いに注意
df_sns$nationality %>%
sort(decreasing = TRUE) %>% # アルファベットの降順ソートを行う
fct_inorder() %>%
levels()
# fct_infreq()... 頻度による順序づけ
df_sns$nationality %>%
fct_infreq() %>%
levels()
```
既存の水準の並びを変更する関数として、このほか、`fct_reorder()`や`fct_rev()`、`fct_shift()`などがある(表XX)。これらは下記のような作図コード中での利用が効果的になるだろう。
<!-- forcatsの並び替え関数 -->
| 関数 | 並び替えの方法 |
|------|----------------|
| `fct_infreq()` | 頻度で順位をつける |
| `fct_inorder()` | 出現した順番にする |
| `fct_relevel()` | 文字列による順番の指定 |
| `fct_reorder()` | カウントや最大値など異なる水準値を指定 |
| `fct_rev()` | 順番の反転 |
| `fct_shift()` | 指定した数だけずらす |
| `fct_shuffle()` | ランダム |
```{r fig-ch4-1-forcats, eval = TRUE, echo = TRUE, fig.width = 9, fig.cap = "forcatsパッケージの水準並び替え"}
library(ggplot2)
p1 <- df_count %>%
ggplot(aes(f, n)) +
geom_bar(stat = "identity") +
ggtitle("default")
# 別水準(ここでは頻度)による並び替え
p2 <- df_count %>% ggplot(aes(fct_reorder(f, n), n)) +
geom_bar(stat = "identity") +
ggtitle("fct_reorder")
# 順位を逆転させる
p3 <- df_count %>%
ggplot(aes(fct_rev(fct_reorder(f, n)), n)) +
geom_bar(stat = "identity") +
ggtitle("fct_rev")
# fct_shiftでは引数nに指定した値分、順位をずらす
p4 <- df_count %>%
ggplot(aes(fct_shift(fct_reorder(f, n), n = 3), n)) +
geom_bar(stat = "identity") +
ggtitle("fct_reorder")
gridExtra::grid.arrange(p1, p2, p3, p4, ncol = 2)
```
#### 水準の変更、追加・削除
因子型のベクトルの水準の値を手動で変更する関数として`fct_recode()`を紹介する。この関数は、新たに定義したい水準とそれに含まれる要素を文字列で指定する。また、水準から除外する要素がある場合は*NULL*を指定する。
```{r}
(x <- as_factor(df_sns$nationality[1:5]))
# US, THの値を「アメリカ」、「タイ」に変換
# IDをNAとして処理し、水準から除外する
x %>%
fct_recode(
`アメリカ` = "US",
`タイ` = "TH",
NULL = "ID"
)
```
因子型のベクトルは、個々の要素に与えられた水準が与えられており、次のように一部を参照した場合でも元の水準を含んでいる。
```{r}
# 国籍のベクトルを因子型にしたデータの一部を参照
# 水準を引き継ぐ
(x[1:2])
```
使われていない水準を削除するには、標準関数の`droplevels()`が有効であるが、`fct_drop()`を使うことで個別の水準を削除することが可能となる。この関数は`droplevels()`同様、既定では参照されていない水準をすべて除外するが、*only*引数に対象の水準名を指定することで特定の水準のみを削除することができる。なお、水準が参照されている状態では削除は行われない。
```{r}
droplevels(x[1:2])
fct_drop(x[1:2])
# IDのみを削除 (HKを残す)
fct_drop(x[1:2], only = c("ID"))
```
データに大量の水準がある時、あるいは水準をより大きな水準として処理したい時、`fct_collapse()`による変換が役立つ。次の処理は、**df_sns**のnationality (国籍)列を大陸ごとにまとめて新たに水準を与える例である。
```{r}
fct_count(df_sns$nationality, sort = TRUE) %>%
head()
fct_collapse(df_sns$nationality,
Asia = c("CN", "HK", "ID", "IN", "KR",
"MY", "PH", "SG", "TH", "TW"),
America = c("US"),
Europa = c("ES", "FR", "GB"),
Oceania = c("AU")) %>%
fct_count(sort = TRUE)
```
`fct_lump()`は要素の数が多いカテゴリデータに対し、頻度の少ない要素を一つのグループにまとめる処理を実行する。引数*n*で実行後の要素数を定めるか、*prop*では全体の頻度から各要素の頻度を割った値を閾値とし、閾値が*prop*の値未満である時に*other_level*の値が適用される。
```{r}
# nationalityは15要素からなり、合計で500になる
table(df_sns$nationality)
# nで再グループ化後の要素数を定義する
df_sns$nationality %>%
fct_lump(n = 6, other_level = "その他") %>%
table()
# 全体に対するCNの割合は0.128、GBは0.042
# propで閾値を設定する... GBはその他のグループに含まれる
df_sns$nationality %>%
fct_lump(prop = 0.05, other_level = "その他") %>%
table()
```
因子型のベクトルを扱うとき、欠損値は除外して処理されることがある。一方、`fct_explicit_na()`を用いると欠損値を一時的に文字列へと変換が行われる。欠損値の水準は、水準の最後の値として扱われる。これは例えば`table()`を使った次の処理で因子型のベクトルの頻度を数えることが可能となる。
```{r}
nationality <-
df_sns$nationality %>%
as_factor()
# 欠損値は除外されている
table(nationality)
# 欠損値 NAは (Missing) という文字列に置換され、結果に含まれる
nationality %>%
fct_explicit_na() %>%
table()
# 欠損値は水準の最後の値に採用される
c("(aa)", "(zz)", NA) %>%
fct_explicit_na()
```
<!-- df_count$f %>% fct_anon(prefix = "国籍_"), fct_unify(), fct_expand() -->
## 日付・時間データの扱い {#date-and-time}
日付や時間はデータが記録された時点の値が記述されることが多く、データを比較する際の一つの基準として用いられる。例えば定点観測によって得られるログデータでは、時間の推移状態から異常を検知したり、不安定な変動の発生を記録する。日付・時間データは、今日の1日前が昨日、2日後は明後日というように、数値的な算術演算が可能である。一方で日付・時間には表記方法やタイムゾーンの違いを考慮する必要があり、また丸め込みといった処理によってデータが異なるものに変化することがあるため、その扱いには気をつけなければいけない。ここではRにおける日付・時間データの基礎的な操作方法とともにパッケージを使った日付・時間データの処理と応用的な処理の例を見ていくことにする。
### Rにおける日付・時間データ
日付・時間を表す形式(2018年1月1日を示す2018-01-01など)にはさまざまなものがあるが、Rでは、こうした日付を示す文字列を定義しても文字型のデータとみなされる。これを日付として扱うには、`as.*`関数群の中にある`as.Date`関数を利用する必要がある。
```{r}
x <- "2018-02-05"
class(x)
```
```{r}
# 日付を表す文字を変換する
as.Date(x) %>% class()
```
Rでは日付や時間に関するデータの取り扱いのために専用のクラスを用意しており、日付を扱うオブジェクトとしてDate、日付・時間のオブジェクトを表すPOSIXlt並びにPOSIXctと呼ばれるクラスが該当する。これらのクラスは、Dateが日付だけを扱うのに対してPOSIXltおよびPOSIXctは日付と時間(秒単位まで)を扱うという点で異なっている\footnote{POSIXlt()はエポック秒の数値、POSIXct()は各要素をリスト形式でもつ}。日付や時間を示すこれらのクラスは、日数の集計や現在から30分後の時間といった演算処理を可能にするために協定世界時 UTC (Universal Time Coordinated)と呼ばれる基準点を利用している。UTCでは1970年1月1日の0時0分を基準とし、年月日や時間に関する計算はこれを0とみなして実行される。次に示す簡単な例でこの概念を確認してみよう。
```{r}
# 協定世界時での1970年1月1日が数値的に0として扱われることを確認
as.numeric(as.Date("1970-01-01"))
# tz引数に協定世界時 UTCを与えた際の1970年1月1日0時0分を数値化
as.numeric(as.POSIXlt("1970-01-01 00:00:00", tz = "UTC"))
# 日付オブジェクトを生成するas.Date()関数では、1970年1月1日から一日進むごとに数値が1加えられる
as.Date("1970-01-02") %>% as.numeric()
# 時間オブジェクトを生成するas.POSIXlt()関数では1秒ごとに1追加される
as.numeric(as.POSIXlt("1970-01-01 00:00:01", tz = "UTC"))
# 本書執筆時の日付・時間とその数値変換した値の出力とその数値的表現
Sys.Date()
as.numeric(Sys.Date())
Sys.time()
as.numeric(Sys.time())
```
日付・時間を扱うオブジェクトでは、算術演算子を用いた演算処理が可能である。また同一クラスのオブジェクトであれば、オブジェクト間で算術演算が利用できる。例えば、`Sys.Date()`により求めた「今日」の日付から1を引くと「昨日」の日付が出力される。
```{r}
# Dateオブジェクトでは1日を単位1として演算処理を行う
x <- Sys.Date()
# 昨日の日付を求める
x - 1
# 現在の日付から10日後の日付を返す
x + 10
# POSIXctオブジェクトでは1秒を1単位として扱う
x <- Sys.time()
x - 3 * 60 * 60
# クラスが異なるために警告が表示される
Sys.time() - Sys.Date()
# 指定されていない日付・時間は丸め込み処理が行われる
as.POSIXct("1970-01-01") - as.POSIXct("2015-12-25 21:00:00")
```
### lubridateパッケージ {#lubridate}
日付・時間データの処理に特化したパッケージはいくつかあるが、本書では**lubridate**を取り上げる。このパッケージはGarrett GrolemundとHadley Wickhamによって開発されたもので、開発思想として利用者が日付・時間のデータや時間帯の処理を直感的に操作できるようになることを目的としている。またtidyverse群に含まれるパッケージであり、データ操作の**dplyr**やパイプ処理と相性が良い\footnote{代表的なものとしてxts、zooなどがある。}。また**lubridate**の関数が返す値の多くはPOSIXctクラスをもっているため、従来の日付・時間クラスとの演算が可能となっている。
```{r}
library(lubridate)
# 直感的に理解しやすい関数を豊富に備えている
today()
now()
```
#### 日付・時間の生成
`ymd()`や`ymd_hms()`といった関数は利用者が入力した日付・時間を示す文字列を整形して出力する。これらの関数は日付と時間を構成する文字列、年であればyear、月はmonth、日はdayのように、対象の文字列が示す日付・時間の表記形式に対応して読み込みを実行する。`dmy()`のように構成要素の順番を入れ替えることであらゆる日付・時間の表記に対応することができる。対象がベクトルで複数の要素を含んでいる場合でも、日付・時間の構成要素が共通していれば出力が行われる。また日付・時間以外の文字列が含まれている際には日付・時間を示す文字のみ抽出し、暦に用いられる文字列の場合には適当な置換を行う仕様となっている。
```{r}
# 年月日形式の文字列を日付として扱う
ymd("20151225", tz = "Asia/Tokyo")
# 年月日という表記形式に対応する
ymd(c("20151225", "2017-02-24", "2001年8月31日"))
# 表記形式の異なる要素では読み込みに失敗する
ymd_hms(c("20151225 12:30:40", "2017-02-24 12:30:40", "August 31, 2001"))
# 日付・時間の要素のみを対象にする
ymd(c("今日は2017年2月25日です。",
"明日は2017年2月26日です",
"20170227"))
```
他に、日付・時間の個々の構成要素を引数で指定して実行する`make_date()`、`make_datetime()`を用いて日付・時間オブジェクトを生成することができる。
```{r}
make_date(year = 1970, month = 1:3, day = 5:3)
# 指定を省略した要素には0あるいは1が与えられる
make_datetime(1970, 1, 1, hour = 3, sec = 30)
```
#### 日付・時間の演算と更新
日付・時間の算術演算は`+`や`-`演算子を利用して行うことが可能である。数値を与えた場合、算術演算は日付・時間オブジェクトの最小単位に対して実行される。そこで、演算の対象とする単位を**lubridate**の`quick_periods()`を用いて実行するのが効率的である。例えば2月26日から同日の一ヶ月後となる3月26日に変更したいとき、月の日数を考慮して処理しなければいけなかったものが、`months()`を使って以下のように処理できる。`quick_periods()`の関数群の名前は日付・時間の単位を複数形で表現したものとなる。
```{r}
# 算術演算子を使った処理
ymd("20160226") - 7
ymd_hms("20160226 04:55:21") + 20
# 3月26日を求める
ymd("20160226") + 29
ymd("20160226") + months(1)
ymd("19891127") + years(28) + months(3) - days(30)
```
ただし`+`や`-`演算子を使った処理では、末日が現実に存在しない値を得る場合に処理が失敗する。具体的には11月の末日30日であるが2月は28日で終わる(閏年の場合は29日まである)。そのため、次のように11月30日から1ヶ月ごとの末日を得ようとすると2月の値は演算に失敗してしまう。**lubridate**が提供する`%m+%`演算子は、この問題を考慮し、演算を実行する。
```{r}
# 1989年11月から1ヶ月刻みで増やしていく
ymd("19891130") + months(1:12)
ymd("19891130") %m+% months(1:12)
ymd("19891130") %m+% years(1:12)
```
関数`update()`を使い、日付・時間への修正を加えることもできる。この関数は引数に日付・時間の構成要素名を与えて実行する。
```{r updateを使った日付時間の更新}
(x <- now())
update(x, year = 1989)
update(x, hour = 11, minute = 30, second = 00)
```
#### 期間
日付・時間において2つの時刻を扱う場合には期間という概念が発生する。そのため**lubridate**パッケージでは、日付・時間に関する特殊なオブジェクトとして期間に関する3つのクラスを提供する。これらは`interval()`、`duration()`、`period()`という関数名が示す通り、それぞれ、時刻Aから時刻Bまでの間を示す期間、長さとして与えられる時間の間隔、時間の単位で構成される長さを示すものとなっている。この中で特に`duration()`は、継続中の時間を示す期間の概念である。
2つの任意の時刻における間の時間を求める際には`interval()`が関数が役に立つ。また関数の代わりに演算子`%--%`を利用しても良い。
```{r}
# 1970年1月1にから今日までのIntervalクラスオブジェクトを作る
time_int <-
interval(ISOdate("1970", "01", "01"), today(), tzone = "Asia/Tokyo")
time_int
interval(as.Date("2016-01-01"), today())
# %--%演算子によるIntervalクラスオブジェクトの生成
ISOdate("2016", "01", "01") %--% today()
```
Intervalに関しては、Intervalオブジェクトであるかを検証する`is.interval()`をはじめとし、期間の始点と終点を返す`int_start()`および`int_end()`、2つの期間の重なりを検知する`int_overlaps()`などが適用できる。また`int_shift()`や`int_start()`、`int_end()`を使って既存の期間の値を変更することが可能である。
```{r}
# Intervalオブジェクトに対する検証
is.interval(time_int)
# 期間の始点と終点を返す
int_start(time_int)
int_end(time_int)
# 期間を反転させる
int_flip(time_int)
# 期間の長さを秒単位で表示する(1日の長さを示す)
int_length(interval(today() - 1, today(), tzone = "Asia/Tokyo"))
# 期間中の時間を標準化させる
int_standardize(time_int)
# 期間をずらす
int_shift(time_int, duration(days = 30))
int_shift(time_int, duration(days = -5))
```