-
Notifications
You must be signed in to change notification settings - Fork 6
/
programming.qmd
1270 lines (891 loc) · 74.7 KB
/
programming.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
# Rプログラミングの基礎 {#sec-programming}
```{r}
#| include: false
knitr::opts_chunk$set(dev = "ragg_png", attr.source='.numberLines')
```
この章では統計ソフトウェアではなく、プログラミング言語としてのRについて解説する。プログラミングは難しいというイメージがあるかもしれないが、実は難しい。しかし、プログラミングをする場合にまず覚えるべき重要概念は、
1. データの読み書き[^prog-reading]
2. 条件分岐
3. 反復
の3つだけだ[^prog-turing]。この3つだけでほとんどのプログラムが作れる。ただ、この単純さがプログラミングの難しさもあるとも言える。
[^prog-reading]: 具体的にはメモリの特定箇所にデータを書き込んだり、それを読み取んだりすること。
[^prog-turing]: チューリング完全 (Turing-complete) な言語の条件に「反復」はない。条件分岐でループを実行することができるからだ。よって、プログラミングを構成するのは「データの読み書き」と「条件分岐」のみとも考えられる。
たとえば、ある数字の列を小さい順(昇順)に並び替えることを考えよう。`c(6, 3, 7, 2, 5, 1, 8, 4)` という数列が与えられたとき、人間ならあまり苦労することなく、`c(1, 2, 3, 4, 5, 6, 7, 8)` に並び替えられるだろう。しかし、その手順を「代入」、「条件分岐」、「反復」のみで明確に表現できるだろうか[^prog-teiban]。もちろん、できる。よって、並べ替えはプログラミングによって実現することができる。Rには、数字を並べ替えるための`sort()`関数や`order()`関数などが用意されており、それらの組込関数を使えば良いのだが、組込関数は「代入」、「条件分岐」、「反復」を組み合わせて作られたものだ。
[^prog-teiban]: 並べ替え(ソート)アルゴリズムはプログラミングを学習する際に定番の題材であり、様々なアルゴリズムがある。
「代入」、「条件分岐」、「反復」という3つの概念さえ理解すれば何でもできるるという意味で、プログラミングは簡単だ。しかし、この3つだけで解決しないといけないという意味で、プログラミングは難しい。頻繁に利用する機能については、ほとんどのプログラミング言語があらかじめ作られた関数 (built-in function, 組込関数) を数多く提供しており、ユーザである私たちは組込関数を使うのが賢明である。それでも条件分岐や反復について勉強する必要があるのは、私たち一般ユーザにとってもこれが必要な場面が多いからである。たとえば、同じ分析をデータだけ変えながら複数回実行するために反復を利用する。反復をうまく使えば、コードの量を数十分の一に減らすことも可能である。また、特定の条件に応じてデータを分類するときは条件分岐を使う。条件分岐を使いこなせないと、CalcやExcelなどのスプレッドシートでデータを目でみながら値を入力するという苦行を強いられるが[^prog-eyeball]、条件分岐が使えればそのような作業は必要ない。数行のプログラミングをするだけで、ほんの数秒で分類が終わる。
[^prog-eyeball]: 苦行であるだけでなく、スプレッドシート上でデータを書き換えると、入力ミスをおかしやすいし、そのミスを後で見つけるのが非常に困難である。したがって、データをスプレッドシート上で編集するのは絶対にやめるべきである。
## R言語の基礎概念 {#sec-programming_intro}
### オブジェクト
これまで「オブジェクト」や「関数」などという概念を定義せずに使ってきたが、ここではR言語の構成する基本的な概念についてもう少し詳細に解説する。これらは、プログラミングをする上である程度は意識的に使う必要があるものである。
まず、**オブジェクト (object)** とはメモリに割り当てられた「何か」である。「何か」に該当するのは、ベクトル (vector)、行列 (matrix)、データフレーム (data frame)、リスト (list)、関数 (function) などである。一般的に、オブジェクトにはそれぞれ固有の(つまり、他のオブジェクトと重複しない)名前が付いている。
たとえば、1から5までの自然数の数列を
```{r}
my_vec1 <- c(1, 2, 3, 4, 5) # my_vec1 <- 1:5 でも同じ
```
のように`my_vec1`という名前のオブジェクトに格納する。オブジェクトに名前をつけてメモリに割り当てると、その後 `my_vec1` と入力するだけでそのオブジェクトの中身を読み込むことができるようになる。
ここで、次のように `my_vec1`の要素を2倍にする操作を考えてみよう。
```{r}
my_vec1 * 2
```
`my_vec1`は、先ほど定義したオブジェクトである。では`2`はどうだろうか。2はメモリに割り当てられていないので、オブジェクトではないのだろうか。実は、この数字 `2` もオブジェクトである。計算する瞬間のみ`2`がメモリに割り当てられ、計算が終わったらメモリから消されると考えれば良い。「Rに存在するあらゆるものはオブジェクトである (Everything that exists in R is an object)」 [@Chambers:2016]。`*` のような演算子でさえもオブジェクトである。
### クラス
**クラス (class)** とはオブジェクトを特徴づける属性である。既に何度か `class()` 関数を使ってデータ型やデータ構造を確認したが、`class()`関数でオブジェクトのクラスを確認することができる。先ほど、`my_vec1`も`*`も`2`もオブジェクトであると説明した。これらがすべてオブジェクトであるということは、何らかのクラス属性を持っているというこである。また、`class()`関数そのものもオブジェクトなので、何らかのクラスをもつ。確認してみよう。
```{r}
class(my_vec1)
class(`*`)
class(2)
class(class)
```
統計分析をする際に、Rのクラスを意識することはあまりない。しかし、Rでパッケージを開発したり、複雑な関数を自作する場合、オブジェクト指向プログラミング (Object-oriented Programming; OOP) の考え方が重要で、オブジェクトのクラスを厳密に定義する必要がある[^prog-class]。
[^prog-class]: RはOOPを実装するためにS3クラスをデフォルトで採用しているが、これはオブジェクト指向プログラミング言語としては厳密性を欠くところがある。Rでは、S3の代わりにS4やR6などの現代的なOOPを実装することもできる。
自分でパッケージを開発しないとしても、クラスの概念は知っておいたほうが良い。この後説明する関数には引数というものがあるが、引数には特定のクラスのオブジェクトしか指定できない。関数の使い方がわからないときには`?関数名` でヘルプを表示するが、ヘルプには各引数に使うべきクラスが書かれている。クラスについて何も知らないと、ヘルプを読んでも意味がわからないかもしれない。
たとえば、Rコンソール上で`?mean`を入力して実行すると、 @fig-programming_help のような `mean()` 関数のヘルプが表示される。ヘルプに示されているとおり、`mean()`関数に必要な引数は `x`、`trim`、`na.rm`である。仮引数`x`の説明を読むと、`x` の実引数にはnumeric または logical のベクトルクラスが使えることが分かる。さらに、date、date-time、time interval が使えることも教えてくれる。
![`mean()`関数のヘルプ画面](Figs/Programming/help.png){#fig-programming_help width="75%" fig-align="center"}
回帰分析を行う`lm()`関数の場合、`data` という仮引数があるが、`data`の実引数に指定できるのは data.frame クラスまたは tibbleクラスのオブジェクトである[^prog-tibble]。通常はクラスを意識せずにRを使っても問題ないが、
1. 全てのオブジェクトにはクラスが付与されており、
2. 関数 (の引数) ごとに実引数として使えるクラスが異なる
という2点は覚えておこう。
[^prog-tibble]: 厳密には、tibble型データは3つのクラスを内部に有しており、その中にdata.frameが含まれる。
### 関数と引数
**関数 (function)** は、入力されたデータを内部で決められた手順に従って処理し、その結果を返すものである。「Rで起こるあらゆることは関数の呼び出しである (Everything that happens in R is a function call)」[@Chambers:2016]。
関数は `関数名(関数の入力となるオブジェクト)` のように使う。たとえば、`class(my_vec1)`は`my_vec1`というオブジェクトのクラスを返す関数である。また、`sum(my_vec1)`は`my_vec1`の要素の総和を計算して返す関数である。
関数は自分で作成することもできる(次章で説明する)。複雑な作業を繰り返す場合、その作業を関数として記述することで、一行でその作業を再現することが可能になる。「2度以上同じことを繰り返すなら関数を作れ」というのが、Rユーザの心得である。
関数を使うためには、 **引数 (ひきすう)** と呼ばれるものが必要である。例えば、`sum()` 関数はこれだけだと何もできない。何らかのオブジェクトがが入力として与えられないと、結果を返すことができない。`sum(my_vec1)`のようにすることではじめて結果が返される。ここで`my_vec1`が`sum()`関数の引数である。
関数は引数は複数持つことができる。たとえば、欠測値を含む以下の`my_vec2`を考えてみよう。
```{r}
my_vec2 <- c(1, 2, 3, NA, 5)
```
この数列の総和を `sum()`関数で求めようとすると、結果は欠測値 `NA` になる。
```{r}
sum(my_vec2)
```
これは`sum()`の基本仕様が「入力に欠測値が含まれている場合は欠測値を返す」ことになっているためである。入力されたデータのなかで欠測値を除いたものの総和を求めるために、`sum()`はもう1つの引数が用意されている。それが `na.rm` である。`na.rm = TRUE`を指定すると、欠測値を除外した総和を返す。
```{r}
sum(my_vec2, na.rm = TRUE)
```
第[-@sec-rbasic]章で説明したとおり、`na.rm` などのように引数を区別するために関数によって用意されたものを**仮引数 (parameter)** と呼び、`TRUE` のように引数の中身としてユーザが指定するものを**実引数 (argument)** と呼ぶ。`my_vec2` は実引数である。
引数を指定するとき、`my_vec2`のように仮引数を明示しないこともある。多くの場合、それがなければ関数がそもそも動かないという第1引数の仮引数は明示しないことが多い。そもそも、第1引数には仮引数がない(名前がない)場合もある。`sum()` 関数の第1引数はnumeric または complex 型のベクトルだが、仮引数がそもそもない(`...` で定義されている)。
しかし、第1引数以外については、`na.rm = TRUE ` のように仮引数を明示すべきである。関数によっては数十個の引数をとるものもあり、仮引数を明示しないと、どの実引数がどの仮引数に対応するのかわかりにくい。
多くの引数は関数によって定義された既定値 (default value) をもっている。たとえば、`sum()`関数の `na.rm` の既定値は `FALSE` である。既定値が用意されている場合、その引数を指定せずに関数を使うことができる。
ある関数がどのような引数を要求しているか、その既定値は何か、引数として何か決められたデータ型/データ構造があるかを調べたいときは `?関数名` (`()`がないことに注意)または `help(関数名)`をコンソールに入力する。多くの関数に詳細なヘルプが付いているので、ネットで検索したり、誰かに質問する前にひとまずヘルプを読むべきである。
## Rのコーディングスタイル {#sec-programming_style}
Rコードの書き方に唯一の正解はない。文法が正しければ、つまり、Rが意図どおりの実行結果を出してくれさえすれば、それは「正しい」コードである。しかし、コードというのは、一度書けば二度と読まないというものではない。最初に「書く」とき以外に、「修正する」ときや「再利用」するときなどに繰り返し読むことになる。また、共同研究をする場合には共同研究者がコードを読む。さらに、近年では研究成果を報告する際に分析に利用したコードを<s>後悔</s>公開することも当たり前になりつつあり、その場合には自分で書いたコードを世界中の人が読む可能性がある。
ここで大事なのは、Rコードを読むのはRだけではないということである。人間も重要な「読者」である。Rコードを書くときは人間に優しいコードを書くように心がえよう。唯一の正解がなくても、読みやすく、多くの人が採用している標準的な書き方はある。ここでは、人間に優しいコードを書くためのコーディングスタイルについて説明しよう。
### オブジェクト名
ベクトル、データフレーム、自作の関数などのすべてのオブジェクトには名前をつける必要がある(ラムダ式などの無名関数を除く)。名前はある程度自由に付けることができるが、大事な原則がある。それは、
- **オブジェクト名は英数字と限られた記号のみにする**
- **数字で始まる変数名は避ける**
1つ目は原則にすぎない。よって、日本語やハングルの変数名をつけることもできる。しかし、それは推奨しない。英数字以外(マルチバイト文字)は文字化けの可能性がある(文字コードの問題が発生する)し、コードを書く際列がずれる原因にもなる
```{r}
var1 <- c(2, 3, 5, 7, 11) # 推奨
```
```{r}
#| include: false
if (Sys.info()["sysname"] == "Windows") {
invisible(Sys.setlocale("LC_ALL", 'Japanese'))
}
```
```{r}
変数1 <- c(2, 3, 5, 7, 11) # 非推奨
```
```{r}
var1
```
```{r}
#| include: false
if (Sys.info()["sysname"] == "Windows") {
invisible(Sys.setlocale("LC_ALL", 'Japanese'))
}
```
```{r}
変数1
```
```{r}
#| include: false
if (Sys.info()["sysname"] == "Windows") {
invisible(Sys.setlocale("LC_ALL", 'Japanese'))
}
```
2つ目のルールは必ず守る必要がある。つまり、数字で始まるオブジェクト名は作成できない。
```{r}
#| error: true
100A <- "R"
```
**予約語を避ける**
Rが提供する組込の関数やオブジェクトと重複する名前を自分で作成するオブジェクトに付けるのは避けよう。例えば、Rには円周率 ($\pi$) が`pi`という名前で用意されている。
```{r}
pi
```
`pi`という名前で新しい変数を作ることはできる。
```{r}
#| error: true
pi <- 777
```
しかし、既存の`pi`が上書きされてしまうので避けたほうが良い。
```{r}
pi # もはや円周率ではない
```
元の円周率を使うこともできるが、手間が増える。
```{r}
base::pi
```
また、ユーザが自由に使えない名前もある。それらの名前を「予約語」と呼ぶ。予約語の使用は禁止されている。
```{r}
#| error: true
if <- "YY"
```
```{r}
#| error: true
for <- "JS"
```
```{r}
#| error: true
TRUE <- "いつもひとつ!"
```
このように、予約語はそもそも使えないので、それほど意識する必要はない。
ただし、以下のコードのようなことが起こるので注意してほしい。
```{r}
vals <- 1:5
vals[c(TRUE, TRUE, FALSE, FALSE, TRUE)]
vals[c(T, T, F, F, T)]
```
ここから、`T` は `TRUE`、`F` は `FALSE` と同じ働きをしていることがわかる。ここで、次のコードを実行してみよう。
```{r}
T <- "Taylor"
F <- "Fourier"
vals[c(T, T, F, F, T)]
```
このように、`T` と`F`はあらかじめ使える状態で用意されているものの、予約語ではないので値が代入できてしまう。しかし、`T` と`F` を自分で定義したオブジェクトの名前に使うと混乱の元になるので、使用は避けるのが無難である。
また、`TRUE` と `FALSE` を `T` や`F` で済ませる悪習は廃して常に完全にスペルすべきである。
**「短さ」と「分かりやすさ」を重視する**
オブジェクト名を見るだけでその中にどのようなデータが含まれているか推測できる名前をつけるべきである。たとえば、性別を表す変数を作るなら、
```{r}
var2 <- c("female", "male", "male", "female")
```
ではなく、
```{r}
gender <- c("female", "male", "male", "female")
```
としたほうが良い。`gender` という名前がついていれば、「この変数には性別に関する情報が入っているだろう」と容易に想像できる。
また、オブジェクト名は、短くて読みやすいほうが良い。数学の成績データをもつ次のオブジェクトについて考えよう。
```{r}
mathematicsscore <- c(30, 91, 43, 77, 100)
```
オブジェクト名から、中にどのような情報が含まれるか想像することはできる。しかし、この名前は長く、読みにくい。そこで、次のように名前を変えたほうが良い。
```{r}
MathScore <- c(30, 91, 43, 77, 100)
mathScore <- c(30, 91, 43, 77, 100)
math_score <- c(30, 91, 43, 77, 100)
```
これらの例では、mathematics を math に縮め、`math`と`score`の間に区切りを入れて読みやすくしている。大文字と小文字の組み合わせで区切る方法は**キャメルケース (camel case)**と呼ばれ、大文字から始まるキャメルケースを**大文字キャメルケース (upper camel case)**、小文字から始まるキャメルケースを**小文字キャメルケース (lower camel case)** と呼ぶ。また、`_`(アンダーバー, アンスコ) で区切る方法は**スネークケース (snake case)** と呼ばれる。キャメルケースとスネークケースは、一貫した方法で使えばどちらを使っても良いだろう。
かつては "." を使って単語を繋ぐのが標準的だった時代もあり、組込関数には "." を使ったものも多い。`data.frame()` や `read.csv()` などがその例である。しかし、"." はクラスのメソッドとして使われることがあり、混乱するので使うのは避けたほうが良い。{tidyverse} では、`readr::read_csv()` のように、スネークケースが採用されている。他にも `-`(ハイフン)で区切る**チェーンケース (chain case)**というのもあるが、Rで`-`は「マイナス(減算演算子)」であり、チェーンケースは使えない[^prog-hyphen]。
[^prog-hyphen]: チェーンケースは、COBOLやLISPのような言語では使われる。どうしても使いたければ、変数名全体をバックティック(`` ` ``)で囲んで`` `math-score` <- 10 `` のようにする方法もあるが、もちろん推奨しない。
### 改行
コードは1行が長すぎないように適宜改行する。Rやパッケージなどが提供している関数のなかには10個以上の引数を必要とするものもあり。コードを1行で書こうとするとコードの可読性が著しく低くなってしまう。
1行に何文字入れるべきかについて決まったルールはないが、1行の最大文字数を半角80字にするという基準が伝統的に使われてきた。これま昔のパソコンで使ったパンチカード ( @fig-programming_punchcard )では1行に80個の穴を開けることができたことに由来する。
![パンチカードの例](Figs/Programming/Punchcard.png){#fig-programming_punchcard width="75%" fig-align="center"}
最近は昔と比べてモニタのサイズが大きく、解像度も高いので、80文字にこだわる必要はない。自分のRStudioのSource Paneに収まるよう、切りがいいところで改行しよう。
### スペースとインデント
適切なスペースはコードの可読性を向上させる。以下の2つのコードは同じ内容を実行するが、後者にはスペースがないので読にくい。
```{r}
#| eval: false
# 良い例
sum(my_vec2, na.rm = TRUE)
# 悪い例
sum(my_vec2,na.rm=TRUE)
```
どこにスペースを入れるかについてのルールは特になり。Rは「**半角**スペース」を無視するので、プログラムの動作に関して言えば、**半角**スペースはあってもなくても同じである。標準的なルールとして、「`,`の後にスペース」、「演算子の前後にスペース」などが考えらえる。ただし、`^`の前後にはスペースを入れないことが多い。また、後ほど紹介する`for(){}`、`while(){}`、`if(){}`などのように、関数以外の `()`の前後にはスペースを入れる。それに対し、`sum()` や `read.csv()` などのように、関数のかっこの場合には、関数名と`()`の間にスペースを入れない。
また、スペースを2回以上入れることもある。たとえば、あるデータフレームを作る例を考えよう。
```{r}
#| eval: false
# 良い例
data.frame(
name = c("Song", "Yanai", "Wickham"),
favorite = c("Ramen", "Cat", "R"),
gender = c("Male", "Male", "Male")
)
# 悪い例
data.frame(
name = c("Song", "Yanai", "Hadley"),
favorite = c("Ramen", "Cat", "R"),
gender = c("Male", "Male", "Male")
)
```
上の2つのコードの内容は同じだが、前者のほうが読みやすい。
ここでもう1つ注目してほしいのは、「字下げ (indent)」の使い方である。上のコードは次のように一行にまとめることができるが、読みにくい。
```{r}
#| eval: false
# 邪悪な例
data.frame(name=c("Song","Yanai","Hadley"),favorite=c("Ramen","Cat","R"),fender=c("Male","Male","Male"))
```
このように1行のコードが長い場合、「改行」が重要である。しかし、Rの最も基本的なルールは「1行に1つのコード」なので、改行するとこのルールを破ることになってしまう。そこで、コードの途中で改行するときは、2行目以降を字下げすることで、1つのコードが前の行から続いていることを明確にしよう。前の行の続きの行は2文字(または4文字)分字下げする。こうすることで、「この行は上の行の続き」ということが「見て」わかるようになる。
```{r}
#| eval: false
# 良い例
data.frame(
name = c("Song", "Yanai", "Hadley"),
favorite = c("Ramen", "Cat", "R"),
gender = c("Male", "Male", "Male")
)
# 悪い例
data.frame(
name = c("Song", "Yanai", "Hadley"),
favorite = c("Ramen", "Cat", "R"),
gender = c("Male", "Male", "Male")
)
```
RStudioは自動的に字下げをしてくれるので、RStudioを使っているならあまり意識する必要はない。自動で字下げしてくれないエディタを使っている場合は、字下げに気をつけよう。
### 代入
オブジェクトに値を代入する演算子として、これまで `<-` を使ってきたが、`=`を使うこともできる。R以外の多くのプログラミング言語では、代入演算子として`=`を採用している。しかし、引数の指定と代入を区別するためん、本書では`<-`の使用を推奨する。また、既に説明したとおり、option + `-`(macOSの場合)または Alt + `-`(Windows の場合)というショートカットを使うと、`<-` だけでなく、その前後のスペースを自動的に挿入してくれる。コードが読みやすくなるので、代入演算子は常にショートカットで入力する習慣をつけよう。
この節で説明したコードの書き方は、コーディングスタイルの一部に過ぎない。本書では、できるだけ読みやすいコードを例として示すことを心がけている。最初は本書(あるいは他の本やウェブサイトに掲載されているもののなかで気に入ったもの)のスタイルを真似して書いてみてほしい。コードを書き写しているうちに、自にとって最善の書き方が見えてくるだろう。
コーディングスタイルについては、以下の2つの資料がさらに詳しい。とりわけ、羽鳥先生が書いた[The tidyverse style guide](https://style.tidyverse.org) は事実上の業界標準であり、[Google's Style Guide](https://google.github.io/styleguide/Rguide.html) も このガイドをベースにしている。かなりの分量だが、パッケージ開発などを考えているなら一度は目を通しておいたほうが良いだろう。
* [The tidyverse style guide](https://style.tidyverse.org)
* [Google's Style Guide](https://google.github.io/styleguide/Rguide.html)
## 反復 {#sec-programming_iteration}
人間があまり得意ではないが、コンピュータが得意とする代表的な作業が「反復作業」である。普通の人間なら数時間から数年かかるような退屈な反復作業でも、コンピュータを使えば数秒で終わることが多い。Rでもさまざまな反復作業を行うことができる。
反復作業を行うときは、反復作業を「いつ終わらせるか」を考えなくてはならない。終わりを規程する方法として、以下の2つが考えられる。
1. 処理を繰り返し回数を指定し、その回数に達したら終了する: `for` 文を使う
2. 一定の条件を指定し、その条件が満たされるときに処理を終了する: `while` 文を使う
この節では、これら2つのケースのそれぞれについて解説する。
### `for` による反復
`for`を利用した反復を、**forループ** と呼ぶ。まず、forループの雛形をみてみよう。
```r
for (任意の変数 in ベクトル) {
処理内容
}
```
任意の変数の選び方はいろいろ考えられるが、よく使うのはインデクス (index) `i` である。このインデクスはforループの内部で使うために用いられる変数である。そして、`in` の後の「ベクトル」には長さ1以上のベクトルを指定する。ベクトルは必ずしもnumeric型である必要はないが、インデクスを利用したforループではインデクスを指定する正の整数を要素にもつベクトルを使う。
また、`{}`内の内容が1行のみの場合には、`{}`は省略しても良い。その場合、処理内容を`()`の直後に書。つまり、以下のような書き方もできる。
```r
for (任意の変数 in ベクトル) 処理内容
```
これは`for`だけでなく、`if`や`function()`など、`{}`で処理内容を囲む関数に共通である。処理内容が2行以上の場合は、必ず`{}`で囲むようにしよう。
例として、インデクス $i$ の内容を画面に表示することを`N`回繰り返すforループを書いてみよう。繰り返し回数を指定するために、`for (i in 1:N)`と書く。5回繰り返すなら`for (i in 1:5)`とする。`1:5`は`c(1, 2, 3, 4, 5)`と同じなので、`for (i in c(1, 2, 3, 4, 5))`でもいいが、`1:5` を使って書いたほうが、コードが読みやすい。
以下コードを作成し、実行してみよう。このコードは3行がひとつのかたまりになったコードである。`for`の行(あるいは最後の`}`の行)にカーソルを置いた状態で Cmd/Ctrl + Return/Enter を押せば、forループ全体が一挙に実行される。
```{r}
# 以下のコードはこのように書くことも可能
# for(i in 1:5) print(i)
for (i in 1:5) {
print(i)
}
```
1から5までの数字が表示された。
ここで、このforループがどのような順番で処理を実行したのか確認してみよう。上のコードは、次のようなステップを踏んで1から5までの数字を画面に表示している。
1. `i`にベクトル`1:5`の最初の要素を代入 (`i <- 1`)
2. `print(i)`を実行
3. `{}`中身の処理が終わったら`i`にベクトル`1:5`内の次の要素を代入 (`i <- 2`)
4. `print(i)`を実行
5. `{}`中身の処理が終わったら`i`にベクトル`1:5`内の次の要素を代入 (`i <- 3`)
6. `print(i)`を実行
7. `{}`中身の処理が終わったら`i`にベクトル`1:5`内の次の要素を代入 (`i <- 4`)
8. `print(i)`を実行
9. `{}`中身の処理が終わったら`i`にベクトル`1:5`内の次の要素を代入 (`i <- 5`)
10. `print(i)`を実行
11. `{}`中身の処理が終わったら`i`にベクトル`1:5`内の次の要素を代入するが、`5`が最後の要素なので反復終了
この手順を要約すると、次のようになる。
* 任意の変数 (ここでは`i`)にベクトル (ここでは`1:5`)の最初の要素が格納され、`{}`内の処理を行う。
* `{}`内の処理が終わったら、ベクトル (ここでは`1:5`)の次の要素を任意の変数 (ここでは`i`)に格納し、`{}`内の処理を行う。
* 格納できる要素がなくなったら反復を終了する。
* したがって、反復はベクトル (ここでは`1:5`)の長さだけ実行される (ここでは5回)。
反復回数はベクトルの長さで決まる。既に述べたとおり、`1:5`のような書き方でなく、普通のベクトルを指定しても問題ない。たとえば、長さ6の `dmg_vals` というベクトルを作り、その要素を出力するコードを書いてみよう。
```{r}
dmg_vals <- c(24, 64, 31, 46, 81, 102)
for (damage in dmg_vals) {
x <- paste0("トンヌラに", damage, "のダメージ!!")
print(x)
}
```
スライムくらいなら一撃で撃破できそうな立派な勇者である。
ちなみに、`paste0()`は引数を空白なし[^prog-paste]で繋いだ文字列を返す関数である。詳細は第[-@sec-string]で解説するが、簡単な例だけ示しておこう。
[^prog-paste]: `paste()` 関数を使うと、`sep`で指定した文字列で繋ぐ。`sep` の既定値は半角スペース。
```{r}
paste0("Rは", "みんなの", "ともだち")
paste0("私の", "HP/MPは", 500, "/", 400, "です。")
```
文字列のベクトルを使ったforループもできる。10個の都市名が格納された`cities`の要素を1つずつ出力するコードは次のように書ける。
```{r}
cities <- c("Sapporo", "Sendai", "Tokyo", "Yokohama", "Nagoya",
"Kyoto", "Osaka", "Kobe", "Hiroshima", "Fukuoka")
for (i in seq_along(cities)) {
x <- paste0("現在、cityの値は", cities[i], "です。")
print(x)
}
```
ここでは `for (i in 1:10)` ではなく、`for (i in seq_along(cities))`と表記。この例からわかるように、`seq_along()` を使うと、ベクトルのインデクス自動的に作ってくれる。上の例では、`seq_along(ten_cities)` が自動的にベクトル長さを計算し、`1:length(ten_cities)` すなわち `1:10` と同じ処理をしてくれる。ベクトルの長さが自明でないとき(ベクトルが非常に長いとき)には、`seq_along()` を使うのが便利である。後で cities の中身を書き換えたときに、cities の長さが変わることも考えられるので、ベクトルのインデクス指定には `seq_along()` を使おう。
また、インデクスを使わないforループも書ける。上と同じ結果は、次のコードで実現することができる。
```{r}
for (city in cities) {
x <- paste0("現在、cityの値は", city, "です。")
print(x)
}
```
このように、ベクトルの中身が数字でない場合でも、forループでベクトルの要素を1つずつ順番に利用することができる。
次に、`"1番目の都市名はSapporoです"`、`"2番目の都市名はSendaiです"`、... のように出力する方法を考えよう。これまでは出力する文字列のなかで1箇所(都市名)のみを変えながら表示したが、今回は「**`i`**」と「**`city`**」の2箇所を同時に変える。これは、インデクス `i`を使って反復を実行しながら、`cities` の`i`番目要素を呼び出すことで実現できる。以下のコードを実行してみよう。
```{r}
for (i in seq_along(cities)) {
msg <- paste0(i, "番目の都市名は", cities[i], "です。")
print(msg)
}
```
このように、インデクスとそれに対応するベクトルの要素を抽出すれば、望みどおりの処理ができる。
**多重forループ**
forループの中でさらにforループを使うともできる。最初はやや難しいかもしれないが、多重反復はプログラミング技術としてよく使われるので[^prog-multiple_interation]、この機会に勉強しよう。
[^prog-multiple_interation]: しかし、forループをあまりにも多く重ねるとコードの可読性が低下するので注意しよう。多重の反復処理が必要な場合には、反復処理のための関数を自作することで対応することもできる。
多重forループを理解するためにうってつけの例は掛け算九九である。積算演算子 `*` の「左の数」と「右の数」 のそれぞれに1から9までの数字を代入するすべての組み合わせを考えるためには、2つのforループが必要だ[^prog-commutative]。`i * j`で`i`と`j`それぞれに1から9を代入しながら結果を出力するコードは次のように書ける。
```{r}
for (i in 1:9) {
for (j in 1:9) {
print(paste(i, "*", j, "=", i * j))
}
}
```
[^prog-commutative]: 交換法則が成り立ち、演算子の左右を区別する意味はないので、本当は81パタンも考える必要はない。
上のコードがどのような処理をしているか考えてみよう。まず、`i`に1が代入される。次に、`j`に1から9までの数順番に1つずつ代入され、`1 * 1`、`1 * 2`、`1 * 3`、...、`1 * 9`が計算される。`1 * 9`の計算が終わったら、`i`に2が代入される。そして再び内側のforループが実行され、`2 * 1`、`2 * 2`、`2 * 3`、...、`2 * 9`が計算される。これを9の段まで繰り返す。段の順番を降順にして9の段を最初に計算し、1の段を最後に計算したい場合は、`i in 1:9`を`i in 9:1`に変えればよい。
```{r}
for (i in 9:1) {
for (j in 1:9) {
print(paste(i, "*", j, "=", i * j))
}
}
```
九九を覚えるときにはこれで良いが、実数どうしの掛け算で `i * j`と`j * i` は同じ(交換法則が成り立つ)なので、2つの数字の積を知りたいだけなら片方だけ出力すれば十分だろう。そこで、`for (j in 1:9)` を `for (j in i:9)` に変えてみよう。
```{r}
for (i in 1:9) {
for (j in i:9) {
print(paste(i, "*", j, "=", i * j))
}
}
```
このコードは、1の段は`1 * 1`から`1 * 9` までのすべてを計算するが、2の段は`2 * 2`から、3の段は`3 * 3`から計算を始めて出力するコードである。2の段の場合、`2 * 1`は1の段で `1 * 2` が計算済みなのでスキップする。3の段の場合、`3 * 1` は1の段で、`3 * 2` は2の段でそれぞれ計算済みなのでとばす。
それぞれの段で同様の処理を続け、最後の9の段では`9 * 9` のみが計算される。
このように多重forループは複数のベクトルの組み合わせて処理を行う場合に便利である。
複数のベクトルでなく、データフレームなどの2次元以上データにも多重forループは使われる。データフレームは複数のベクトルで構成されている(各列が1つのベクトル)ため、実質的には複数のベクトルを扱うことになる。
例として、[FIFA_Men.csv](/Data/FIFA_Men.csv) を利用し、それぞれの国のチーム名、FAFAランキング、ポイントをまとめて表示するコードを書いてみよう。すべてのチームを表示すると結果が長くなるので、対象をOFC (オセアニアサッカー連盟) 所属チームに限定する。
```{r}
#| message: false
# read_csv()を使用するために{tidyverse}を読み込む
pacman::p_load(tidyverse)
# FIFA_Men.csvを読み込み、myDFという名で保存
my_df <- read_csv("Data/FIFA_Men.csv")
# my_dfのConfederation列がOFCの行だけを抽出
my_df <- my_df[my_df$Confederation == "OFC", ]
my_df
```
OFCに10チーム加盟していることがわかる。
以下のような内容が表示されるコードを書きたい。
```
=====1番目のチーム情報=====
Team: American Samoa
Rank: 192
Points: 900
=====2番目のチーム情報=====
Team: Fiji
Rank: 163
Points: 996
=====3番目のチーム情報=====
Team: New Caledonia
...
```
これは1つのforループでも作成できるが、勉強のために2つのforループを使って書いてみよう。
```{r}
#| eval: false
for (i in 1:nrow(my_df)) {
print(paste0("=====", i, "番目のチーム情報====="))
for (j in c("Team", "Rank", "Points")) {
print(paste0(j, ": ", my_df[i, j]))
}
}
```
以上のコードを実行すると以下のような結果が表示される(全部掲載すると長くなるので、ここでは最初の2チームのみ掲載する。
```{r}
#| echo: false
for (i in 1:2) {
print(paste0("=====", i, "番目のチーム情報====="))
for (j in c("Team", "Rank", "Points")) {
print(paste0(j, ": ", my_df[i, j]))
}
}
```
このコードについて説明しよう。まず、外側のforループでは任意の変数としてインデクス`i`を、内側のforループではインデクス`j`を使っている。Rは、コードを上から順番に処理する(同じ行なら、カッコの中から処理する)。したがって、まず処理されるのは外側のforループである。`i`にはベクトル`1:nrow(my_df)`の要素が順番に割り当てられる。`nrow()` は行列またはデータフレームの行数を求める関数で、`my_df`は10行のデータなので`1:10`になる。つまり、外側のforループは`i`に1, 2, 3, ..., 10の順で値を格納しながらループ内の処理を繰り返す。ここで、外側のforループの中身を見てみよう。まず、`print()` を使って `"=====i番目のチーム情報====="` というメッセージを出力している。最初は`i`が`1`なので、`"=====1番目のチーム情報====="`が表示される。
次に実行されるのが内側のforループである。ここでは`j`に`c("Team", "Rank", "Points")`を格納しながら内側のforループの内のコードをが3回繰り返し処理される。内側のコードの内容は、たとえば、`i = 1`の状態で、`j = "Team"`なら、`print(paste0("Team", ": ", my_df[1, "Team"]))`である。第[-@sec-datastructure-dataframe]章で説明した通り、`my_df[1, "Team"]`は`my_df`の`Team` 列の1番目の要素を意味する。この処理が終わると、次は`j`に`"Rank"`が代入され、同じコードを処理する。そして、`j = "Points"`まで処理が終わったら、内側のforループは一度終了する。
内側のforループが終わっても、外側のforループはまだ終わっていない。次は、`i` に `2` が格納され、`"=====2番目のチーム情報====="` を表示し、**再度**内側のforループを**最初から**処理する。 `i = 10`の状態で内側のforループが終了ると外側のforループも終了する。多重forループはこのように反復処理を実行する。
チーム名の次に所属連盟も表示したいときはどう直せば良いだろうか。正解は`c("Team", "Rank", "Points")`のベクトルで、`"Team"`と`"Rank"`の間に`"Confederation"`を追加するだけである。実際にやってみよう (スペースの関係上、最初の2チームの結果のみ掲載する)。
```{r}
#| eval: false
for (i in 1:nrow(my_df)) {
print(paste0("=====", i, "番目のチーム情報====="))
for (j in c("Team", "Confederation", "Rank", "Points")) {
print(paste0(j, ": ", my_df[i, j]))
}
}
```
```{r}
#| echo: false
for (i in 1:2) {
print(paste0("=====", i, "番目のチーム情報====="))
for (j in c("Team", "Confederation", "Rank", "Points")) {
print(paste0(j, ": ", my_df[i, j]))
}
}
```
ちなみに、1つのforループで同じ結果を実現するには次のようにする。結果は掲載しないが、自分で試してみてほしい。また、`cat()`関数の使い方については`?cat`を参照されたい。ちなみに`\n`は改行コードである。
```{r}
#| eval: false
for (i in 1:nrow(my_df)) {
cat(paste0("=====", i, "番目のチーム情報=====\n"))
cat(paste0("Team:", my_df$Team[i], "\n",
"Rank:", my_df$Rank[i], "\n",
"Points:", my_df$Points[i], "\n"))
}
```
リスト型を対象とした多重forループの例も確認しておこう。複数のベクトルを含むリストの場合、3番目のベクトルの5番目の要素を抽出するには、`リスト名[[3]][5]` のように2つの位置を指定する必要がある。たとえば、3人で構成されたグループが3つあり、それぞれのグループにおける id (例:学籍番号)の順番で名前が格納されている `my_list`を考えてみよう。
```{r}
my_list <- list(A = c("Song", "Wickham", "Yanai"),
B = c("Watanabe", "Toyoshima", "Fujii"),
C = c("Abe", "Moon", "Xi"))
```
ここで、まず各クラスの出席番号1番の人の名字をすべて出力し、次は2番の人、最後に3番の人を表示するにはどうすれば良いだろうか。以下のコードを見てみよう。
```{r}
for (i in 1:3) {
for (j in names(my_list)) {
print(my_list[[j]][i])
}
print(paste0("===ここまでが出席番号", i, "番の人です==="))
}
```
このコードは以下のように動く。
* 1行目: 任意の変数を`i`とし、1から3までの数字を`i`に格納しながら、3回反復作業を行う。
* 3行目: 任意の変数を`j`とし、ここにはリストの要素名(A, B, C)を格納しながら、リストの長さだけ処理を繰り返す。`names(my_list)`は`c("A", "B", "C")`である。
* 4行目: `my_list[[j]][i]`の内容を出力する。最初は`i = 1`、`j = "A"`なので、`print(my_list[["A"]][1])`、つまり、`my_list`から`"A"`を取り出し、そこの`1`番目の要素を出力する
* 7行目: 各ベクトルから`i`番目の要素を出力し、`print(paste0("===ここまでが出席番号", i, "番の人です==="))`を実行する。`i`に次の要素を入れ、作業を反復する。`i`に格納する要素がなくなったら、反復を終了する。
多重forループは3重、4重にすることも可能だが、コードの可読性が低下するため、多くても3重、できれば最大二重までにしておいたほうがよい。3重以上にforループを重ねる場合は、内側のforループを後ほど解説する関数でまとめたほうがいいだろう。
上の例では、リストの各要素に名前 (A, B, C) が付けられていることを利用した。リストに名前がない場合はどうすれば良いだろうか(そもそも、名前のないリストなど作らないほうが良いのだが...)。例えば、次のような場合である。
```{r}
my_list <- list(c("Song", "Wickham", "Yanai"),
c("Watanabe", "Toyoshima", "Fujii"),
c("Abe", "Moon", "Xi"))
```
この場合には、次のようにすればよい。
```{r}
for (i in 1:3) {
for (j in seq_along(my_list)) {
print(my_list[[j]][i])
}
print(paste0("===ここまでが出席番号", i, "番の人です==="))
}
```
内側のforループでリストの要素名を使う代わりに、リストの要素を位置で指定している。
### `while`による反復
`for`によるループは任意の変数にベクトルの要素を1つずつ代入し、ベクトルの要素を使い尽くすまで反復処理を行う。したがって、ベクトルの長さによってループの回数が決まる。
それに対し、「ある条件が満たされる限り、反復し続ける」あるいは「ある条件が満たされるまで、反復し続ける」ことも可能である。そのときに使うのが、`while`による **whileループ**である。whileループの書き方はforループにとてもよく似ている。
基本的な使い方は、以下のとおりである。
```r
while (条件) {
条件が満たされた場合の処理内容
}
```
`for`が`while`に変わり、`()`内の書き方が`(任意の変数 in ベクトル)`から`(条件)`に変わった。この`()`内の条件が満たされる間は`{}`内の内容を処理が繰り返し実行される。1から5までの整数を表示するコードは、forループを使うと次のように書ける。
```{r}
#| eval: false
for (i in 1:5) {
print(i)
}
```
これをwhileループを使って書き直すと、次のようになる。
```{r}
i <- 1
while (i < 6) {
print(i)
i <- i + 1
}
```
whileループを繰り返す条件は`i < 6`である。これにより、「`i`が6未満」なら処理が繰り返される。注意すべき点として、`i` が6未満か否かの判断をループの開始時点で行うので、あらかじめ変数 `i` を用意する必要があることがあげられる。1行めにある `i <- 1` がそれである。そして、`{}`内の最終行に `i <- i + 1` があり、`i`を1ずつ増やしている。`i = 5` の時点では `print(i)` が実行されるが、`i = 6`になると、ループをもう1ど実行する条件が満たされないので、反復が停止する。
`i <- i + 1`のように内容を少しずつ書き換えるさいは、そのコードを置く位置にも注意が必要だ。先ほどのコードのうち、`i <- i + 1` を `print(i)`の前に移動してみよう。
```{r}
i <- 1
while (i < 6) {
i <- i + 1
print(i)
}
```
2から6までの整数が出力された。これは`i`を出力する**前に**`i`に1が足されるためである。`i <- i + 1`の位置をこのままにして、先ほどと同じように1から5までの整数を表示するには、次のようにコードを書き換える。
```{r}
i <- 0
while (i < 5) {
i <- i + 1
print(i)
}
```
変更点したのは、
1.`i`の初期値を0にした
2. `()`内の条件を`(i < 6)`から`(i < 5)`にした
という2点である。
whileループは慣れないとややこしいと感じるだろう。forループで代替できるケースも多い。それでもwhile文が必要になるケースもある。それは先述した通り、「目標は決まっているが、その目標が達成されるまでは処理を何回繰り返せばいいか分からない」場合である。
たとえば、6面のサイコロ投げを考えよう。投げる度に出た目を記録し、その和が30以上に達したときサイコロ投げを中止するにはどうすればいいだろうか。何回サイコロを投げればいいかがあらかじめわからないと、forループは使えない。連続で6が出るなら5回で十分かもしれないが、1が出続けるなら30回投げる必要がある。このように、「反復処理は行うが、何回行えばいいか分からない。ただし、停止条件は知っている」場合にwhileループを使う。
サイコロ投げの例は、以下のコードで実行できる。
```{r}
total <- 0
trial <- 1
while (total < 30) {
die <- sample(1:6, size = 1)
total <- total + die
print(paste0(trial, "回目のサイコロ投げの結果: ", die,
"(これまでの総和: ", total, ")"))
# print()文は以下のような書き方も可能
# Result <- sprintf("%d回目のサイコロ投げの結果: %d (これまでの総和: %d)",
# trial, die, total)
# print(Result)
trial <- trial + 1
}
```
このコードの中身を説明しよう。まず、これまで出た目の和を記録する変数`total`を用意し、初期値として0を格納する。また、何回目のサイコロ投げかを記録するために`trial`という変数を用意し、初期値として1を格納する(コードの1、2行目)。
次に、`while`文を書く。反復する条件は`total`が30未満と設定する。つまり、`total`が30以上になったら反復を終了する(コードの4行目)。
続いて、サイコロを投げ、出た目を`die`という変数に格納します。サイコロ投げは1から6の間の整数から無作為に1つを値を抽出することでシミュレートできる。そこで使われるのが `sample()`関数である(コードの5行目)。`sample()` 関数は与えられたベクトル内の要素を無作為に抽出する。`sample(c(1, 2, 3, 4, 5, 6), size = 1)`は「`c(1, 2, 3, 4, 5, 6)`から1つの要素を無作為に抽出せよ」という意味である(乱数生成を利用したシミュレーションについては後の章で詳しく説明する)。サイコロの目が出たら、その目を`total`の値に足す (コードの6行目)。
その後、「1回目のサイコロ投げの結果: 5 (これまでの総和: 5)」のように、1回の処理の結果を表示する。 (コードの8行目)。最後に`trial`の値を1増やし (コードの14行目)、1度の処理が終了する。
1度の処理が終わったら、whileループの条件判断に戻り、条件が満たされていれば再び処理を繰り返す。これを条件が満たされなくなるまで繰り返す。
この反復処理は、forループで再現することもできる。次のようにすればよい。
```{r}
total <- 0
for (trial in 1:30) {
die <- sample(1:6, 1)
total <- total + die
# print()の代わりにsprintf()を使うことも可能
result <- sprintf("%d回目のサイコロ投げの結果: %d (これまでの総和: %d)",
trial, die, total)
print(result)
if (total >= 30) break() # ()は省略可能
}
```
このコードの中身を説明しよう。事前にサイコロを投げる回数はわからないが、6面サイコロの場合30回以内には必ず合計が30になるので、`trial in 1:30`としておく。あとはwhileループの書き方とほぼ同じだが、forループでは`i`が自動的に更新されるので`i <- i + 1`は不要である。さらに、一定の条件が満たされるときにループを停止する必要がある。そこで登場するのが条件 `if` と`break()`である。条件分岐は次節で説明するが、`if`から始まる行のコードは、「`total`が30以上になったらループから脱出せよ」ということを意味する。このようにループからの脱出を指示する関数が `break()`だ。`break()` 関数は`()`を省略して `break`と書いてもよい。
これは余談だが、結果の表示に今回はこれまで使ってきた`print()`と`paste0()`(または`paste()`)の代わりに、`sprintf()`を使っている。`sprintf()` 内の`%d`はその位置に指定された変数の値を整数として代入することを意味する。`sprintf()`にの引数にはまず出力する文字列を指定し、次に代入する変数を順番に入力する。`%s`は文字 (string) 型、`%f`は実数 (floating-point number) を意味する。`%f`では小数の桁数も指定可能であり、小数第2位まで表示させるには `%.2f` のように表記する。以下のコードは3つの変数の値と文字列を結合する処理を`print(paste0())`と`sprintf()`を用いて書いたもので、同じ結果が得られる。
```{r}
name <- "Song"
bowls <- 50
height <- 176.2
print(paste0(name, "がひと月に食べるラーメンは", bowls, "杯で、身長は", height, "cmです。"))
# %sにnameを、%dにbowlsを、%.1fにheightを小数第1位まで格納し、出力
sprintf("%sがひと月に食べるラーメンは%d杯で、身長は%.1fcmです。", name, bowls, height)
```
ただし、`{}`内の`sprintf()`はそのまま出力されないため、一旦、オブジェクトとして保存し、それを`print()`を使って出力する必要がある。詳細については、[`?sprintf`](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/sprintf)を参照されたい。
今回例として挙げたサイコロ投げは「多くても30回以内に終わる」ことが分かっていたの forループで書き換えることができた。しかし、終了までに必要な反復回数は未知であることもある。目的に応じて `for`と`while`を使い分けることが求められる。
## 条件分岐 {#sec-programming_condition}
### `if`、`else if`、`else` による条件分岐
この節では条件分岐について説明する。条件分岐は、条件に応じて異なる処理を行いときに利用する。`if` による条件分岐の基本形は次のとおりである。
```r
if (条件) {
条件が満たされた場合のみ実行される処理の内容
}
```
`while`を使ったループによく似ているが、`while`文は条件が満たされる限り`{}`の内容を繰り返し実行するのに対し、`if` 文では条件が満たされれときに`{}`の内容が1回だけ実行されて処理が終了するという違いがある。たとえば、名前が格納されているオブジェクト`name`の中身が`"Song"`なら「ラーメン大好き」と出力されるコードを考えてみよう。
```{r}
name <- "Song"
if (name == "Song") {
print("ラーメン大好き")
}
```
`if`文の `()`内の条件は、「`name`の中身が`"Song"`」の場合のみ`{}`内の処理を実行せよということを意味する。`name`の中身が`"Yanai"`ならどうなるだろうか。
```{r}
name <- "Yanai"
if (name == "Song") {
print("ラーメン大好き")
}
```
何も表示されない。このように、`if`文単体だと、条件が**満たされない**場合には何も実行されない。
条件にに応じて異なる処理を実行するために、`else` を使う。これは`if`とセットで使われるもので、`if`の条件が満たされなかった場合の処理内容を指定することができる。`else`を加えると、次のようなコードが書ける。
```r
if (条件) {
条件が満たされた場合の処理内容
} else {
条件が満たされなかった場合の処理内容
}
```
`else`文は、以下のように改行して書くこともできる。
```r
if (条件) {
条件が満たされた場合の処理内容
}
else {
条件が満たされなかった場合の処理内容
}
```
しかし、この書き方はあまり良くない。`else` は必ず `if` とセットで用いられるので、`if` の処理の終わりである `}` の直後に半角スペースを1つ挟んで書くのが標準的なコーディングスタイルである。
それでは `name`が`"Song"`でない場合には、「ラーメン好きじゃない」表示されるコードを書いてみよう。
```{r}
name <- "Song"
if (name == "Song") {
print("ラーメン大好き")
} else {
print("ラーメン好きじゃない")
}
```
`name`が`"Song"`の場合、前と変わらず「ラーメン大好き」が出力される。`name`を`"Yanai"`に変えてみよう。
```{r}
name <- "Yanai"
if (name == "Song") {
print("ラーメン大好き")
} else {
print("ラーメン好きじゃない")
}
```
「ラーメン好きじゃない」が表示される。
しかし、世の中はと「ラーメン大好き」と「ラーメン好きじゃない」のみで構成されているわけではない。「ラーメン大好き」なのはSong と Koike、「ラーメン好きじゃない」のは Yanai
のみで、それ以外の人は「ラーメンそこそこ好き」だとしよう。つまり3つのパタンがある。それぞれに別の処理を実行するには、3つ以上の条件に対応できる条件分岐が必要になる。
そのような場合には `else if`を`if`と`else`の間に挿入する。
```r
if (条件1) {
条件1が満たされた場合の処理内容
} else if (条件2) {
条件1が満たされず、条件2が満たされた場合の処理内容
} else if (条件3) {
条件1, 2が満たされず、条件3が満たされた場合の処理内容
} else {
条件が全て満たされなかった場合の処理内容
}
```
このように条件分岐を書くと、`if`文の条件が満たされれば最初の`{}`内の処理を実行し、満たされなかったら次の`else if`の条件を判定する。その条件が満たされれば`{}`の処理を実行し、満たされなければ次の`else if`へ移動...を順に実施する。どの条件も満たされないときは、最終的に `else` の内容が実行される。
それでは実際にコードを書いてみよう。
```{r}
name <- "Song"
if (name == "Song" | name == "Koike") {
# 上の条件は、(name %in% c("Song", "Koike")) でもOK
print("ラーメン大好き")
} else if (name == "Yanai") {
print("ラーメン好きじゃない")
} else {
print("ラーメンそこそこ好き")
}
```
このコードでは、まず`name`が`"Song"` または `"Koike"` か否かを判定し、`TRUE`なら「ラーメン大好き」を表示する。`FALSE` なら次の `else if`文へ移動する。ここでは`Name`が`"Yanai"`かどうかを判定する。
最初の条件に登場する `|`は「OR (または)」を意味する論理演算子であり、第[-@sec-rbasic]章で解説した。このように、条件の中で`|`や`&`などの論理演算子を使うことで、複数の条件を指定することができる。また、`name == "Song" | name == "Koike"`は`name %in% c("Song", "Koike")`に書き換えることがでる。`x %in% y`は`x`が`y`に含まれているか否かを判定する演算子である。たとえば、`"A" %in% c("A", "B", "C")`の結果は`TRUE`だが、`"Z" %in% c("A", "B", "C")`の結果は`FALSE`になる。
それでは`name`を`"Yanai"`、`"Koike"`、`"Shigemura"`、`"Hakiai"`に変えながら結果を確認してみよう。
```{r}
name <- "Yanai"
if (name %in% c("Song", "Koike")) {
print("ラーメン大好き")
} else if (name == "Yanai") {
print("ラーメン好きじゃない")
} else {
print("ラーメンそこそこ好き")
}
```
```{r}
name <- "Koike"
if (name %in% c("Song", "Koike")) {
print("ラーメン大好き")
} else if (name == "Yanai") {
print("ラーメン好きじゃない")
} else {
print("ラーメンそこそこ好き")
}
```
```{r}
name <- "Shigemura"
if (name %in% c("Song", "Koike")) {
print("ラーメン大好き")
} else if (name == "Yanai") {
print("ラーメン好きじゃない")
} else {
print("ラーメンそこそこ好き")
}
```
```{r}
name <- "Hakiai"
if (name %in% c("Song", "Koike")) {
print("ラーメン大好き")
} else if (name == "Yanai") {