-
Notifications
You must be signed in to change notification settings - Fork 17
/
01-Basics.qmd
697 lines (547 loc) · 48.8 KB
/
01-Basics.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
# Типы данных {#sec-basics}
[Программный код главы](https://github.com/tsamsonov/r-geo-course/blob/master/code/01-Basics.R)
## Типы данных {#data_types}
Тип данных --- это класс данных, характеризуемый членами класса и операциями, которые могут быть к ним применены^[ISO/IEC/IEEE 24765-2010 Systems and software engineering — Vocabulary]. С помощью типов данных мы можем представлять привычные нам сущности, такие как числа, строки и т.д. В языке R существует 5 базовых типов данных:
Название | Тип данных
---------|-----------
`complex` | комплексные числа
`character`| символьный (строки)
`integer` | целые числа
`logical` | логические (булевы)
`numeric` | числа с плавающей точкой
Помимо этого есть тип `Date`, который позволяет работать с датами. Рассмотрим использование каждого из перечисленных типов.
### Числа {#numbers}
Числа — основной тип данных в R. К ним относятся _числа c плавающей точкой_ и _целые числа_. В терминологии R такие данные называются _интервальными_, поскольку к ним применимо понятие интервала на числовой прямой. Целые числа относятся к _дискретным интервальным_, а числа с плавающей точкой — к _непрерывным интервальным_. Числа можно складывать, вычитать и умножать:
```{r, collapse=TRUE}
2 + 3
2 - 3
2 * 3
```
Разделителем целой и дробной части является точка, а не запятая:
```{r, collapse=TRUE}
2.5 + 3.1
```
Существует также специальный оператор для возведения в степень. Для этого вы можете
использовать или двойной знак умножения (`**`) или _циркумфлекс_ (`^`):
```{r, collapse=TRUE}
2 ^ 3
2 ** 3
```
Результат деления по умолчанию имеет тип с плавающей точкой:
```{r, collapse=TRUE}
5 / 3
5 / 2.5
```
Если вы хотите чтобы деление производилось целочисленным образом (без дробной части) необходимо использовать оператор `%/%`:
```{r, collapse=TRUE}
5 %/% 3
```
Остаток от деления можно получить с помощью оператора `%%`:
```{r, collapse=TRUE}
5 %% 3
```
Вышеприведенные арифметические операции являются бинарными, то есть требуют наличия двух чисел. Числа называются "операндами". Отделять операнды от оператора пробелом или нет --- дело вкуса. Однако рекомендуется все же отделять, так как это повышает читаемость кода. Следующие два выражения эквивалентны. Однако сравните простоту их восприятия:
```{r, collapse=TRUE}
5%/%3
```
```{r, collapse=TRUE}
5 %/% 3
```
Как правило, в настоящих программах числа в явном виде встречаются лишь иногда. Вместо этого для их обозначения используют переменные. В вышеприведенных выражениях мы неоднократно использовали число 3. Теперь представьте, что вы хотите проверить, каковы будут результаты, если вместо 3 использовать 4. Вам придется заменить все тройки на четверки. Если их много, то это будет утомительная работа, и вы наверняка что-то пропустите. Конечно, можно использовать поиск с автозаменой, но что если тройки надо заменить не везде? Одно и то же число может выполнять разные функции в разных выражениях. Чтобы избежать подобных проблем, в программе вводят переменные и присваивают им значения. Оператор присваивания значения выглядит как `=`
```{r, collapse=TRUE}
a = 5
b = 3
```
Чтобы вывести значение переменной на экран, достаточно просто ввести его:
```{r, collapse=TRUE}
a
b
```
Мы можем выполнить над переменными все те же операции что и над константами:
```{r, collapse=TRUE}
a + b
a - b
a / b
a %/% b
a %% b
```
Легко меняем значение второй переменной с 3 на 4 и выполняем код заново.
```{r, collapse=TRUE}
b = 4
a + b
a - b
a / b
a %/% b
a %% b
```
Нам пришлось изменить значение переменной только один раз в момент ее создания, все последующие операции остались неизменны, но их результаты обновились!
Новую переменную можно создать на основе значений существующих переменных:
```{r, collapse=TRUE}
c = b
d = a+c
```
Посмотрим, что получилось:
```{r, collapse=TRUE}
c
d
```
Вы можете комбинировать переменные и заданные явным образом константы:
```{r, collapse=TRUE}
e = d + 2.5
e
```
Противоположное по знаку число получается добавлением унарного оператора `-` перед константой или переменной:
```{r, collapse=TRUE}
f = -2
f
f = -e
f
```
Операция взятия остатка от деления бывает полезной, например, когда мы хотим выяснить, является число четным или нет. Для этого достаточно взять остаток от деления на 2. Если число является четным, остаток будет равен нулю. В данном случае c равно 4, `d` равно 9:
```{r, collapse=TRUE}
c %% 2
d %% 2
```
#### Числовые функции {#number_functions}
Прежде чем мы перейдем к рассмотрению прочих типов данных и структур данных нам необходимо познакомиться с функциями, поскольку они встречаются буквально на каждом шагу. Понятие функции идентично тому, к чему мы привыкли в математике. Например, функция может называться `Z`, и принимать 2 аргумента: `x` и `y`. В этом случае она записывается как `Z(x,y)`. Чтобы получить значение функции, необходимо подставить некоторые значения вместо `x` и `y` в скобках. Нас даже может не интересовать, как фактически устроена функция внутри, но важно понимать, что именно она должна вычислять. С созданием функций мы познакомимся позднее.
Важнейшие примеры функций — математические. Это функции взятия корня `sqrt(x)`, модуля `abs(x)`, округления `round(x, digits)`, натурального логарифма `log(x)`, тригонометрические функции `sin(x)`, `cos(x)`, `tan(x)`, обратные к ним `asin(y)`, `acos(y)`, `atan(y)` и многие другие. Основные математические функции содержатся в пакете [`base`](https://stat.ethz.ch/R-manual/R-devel/library/base/html/00Index.html), который по умолчанию доступен в среде R и не требует подключения.
В качестве аргумента функции можно использовать переменную, константу, а также выражения:
```{r, collapse=TRUE}
sqrt(a)
sin(a)
tan(1.5)
abs(a + b - 2.5)
```
Вы также можете легко вкладывать функции одна в одну, если результат вычисления одной функции нужно подставить в другую:
```{r, collapse=TRUE}
sin(sqrt(a))
sqrt(sin(a) + 2)
```
Также как и с арифметическими выражениями, результат вычисления функции можно записать в переменную:
```{r, collapse=TRUE}
b = sin(sqrt(a))
b
```
Если переменной b ранее было присвоено другое значение, оно перезапишется. Вы также можете записать в переменную результат операции, выполненной над ней же. Например, если вы не уверены, что `a` — неотрицательное число, а вам это необходимо в дальнейших расчетах, вы можете применить к нему операцию взятия модуля:
```{r, collapse=TRUE}
b = sin(a)
b
b = abs(b)
b
```
### Строки {#strings}
__Строки__ — также еще один важнейший тип данных. Чтобы создать строковую переменную, необходимо заключить текст строки в кавычки:
```{r, collapse=TRUE}
s = "В историю трудно войти, но легко вляпаться (М.Жванецкий)"
s
```
Строки состоят из символов, и, в отличие от некоторых других языков, в R нет отдельного типа данных для объекта, которых хранит один символ (в C++ для этого используется тип `char`). Поэтому при создании строк вы можете пользоваться как одинарными, так и двойными кавычками:
```{r}
s1 = "Это строка"
s1
s2 = 'Это также строка'
s2
```
Иногда бывает необходимо создать _пустую_ строку (например, чтобы в нее далее что-то добавлять). В этом случае просто напишите два знака кавычек, идущих подряд без пробела между ними:
```{r}
s1 = "" # это пустая строка
s1
s2 = '' # это также пустая строка
s2
s3 = ' ' # а это не пустая, тут есть пробел
s3
```
_Длину_ строки в символах можно узнать с помощью функции `nchar()`
```{r, collapse=TRUE}
nchar(s)
nchar(s1)
nchar(s3)
```
Чтобы извлечь из строки _подстроку_ (часть строки), можно использовать функцию `substr()`, указав ей номер первого и последнего символа:
```{r}
substr(s, 3, 9) # извлекаем все символы с 3-го по 9-й
```
В частности, зная длину строки, можно легко извлечь последние $k$ символов:
```{r}
n = nchar(s)
k = 7
substr(s, n - k, n)
```
Строки можно складывать так же как и числа. Эта операция называется _конкатенацией_. В результате конкатенации строки состыковываются друг с другом и получается одна строка. В отличие от чисел, конкатенация производится не оператором `+`, а специальной функцией `paste()`. Состыковываемые строки нужно перечислить через запятую, их число может быть произвольно
```{r, collapse=TRUE}
s1 = "В историю трудно войти,"
s2 = "но легко вляпаться"
s3 = "(М.Жванецкий)"
```
Посмотрим содержимое подстрок:
```{r, collapse=TRUE}
s1
s2
s3
```
А теперь объединим их в одну:
```{r, collapse=TRUE}
s = paste(s1, s2)
s
s = paste(s1, s2, s3)
s
```
Настоящая сила конкатенации проявляется когда вам необходимо объединить в одной строке некоторое текстовое описание (заранее известное) и значения переменных, которые у вас вычисляются в программе (заранее неизвестные). Предположим, вы нашли в программе что максимальная численность населения в Детройте пришлась на 1950 год и составила 1850 тыс. человек. Найденный год записан у вас в переменную `year`, а население в переменную `pop`. Вы их значения пока что не знаете, они вычислены по табличным данным в программе. Как вывести эту информацию на экран "человеческим" образом? Для этого нужно использовать конкатенацию строк.
Условно запишем значения переменных, как будто мы их знаем
```{r, collapse=TRUE}
year = 1950
pop = 1850
```
```{r, collapse=TRUE}
s1 = "Максимальная численность населения в Детройте пришлась на"
s2 = "год и составила"
s3 = "тыс. чел"
s = paste(s1, year, s2, pop, s3)
s
```
Обратите внимание на то что мы конкатенировали строки с числами. Конвертация типов осуществилась автоматически. Помимо этого, функция сама вставила пробелы между строками.
> Функция `paste()` содержит параметр `sep`, отвечающий за символ, который будет вставляться между конкатенируемыми строками. По умолчанию `sep = " "`, то есть, между строками будет вставляться пробел. Подобное поведение желательно не всегда. Например, если после переменной у вас идет запятая, то между ними будет вставлен пробел. В таком случае при вызове `paste()` необходимо указать `sep = ""`, то есть _пустую строку_: `paste(... sep = "")`. Вы также можете воспользоваться функцией `paste0()`, которая делает [почти] то же самое, что и `paste(..., sep = "")`, но избавляет вас от задания параметра `sep`.
### Даты и длительности {#dates}
Для работы с временными данными в R существуют специальные типы. Чаще всего используются даты, указанные с точностью до дня. Такие данные имеют тип `Date`, а для их создания используется функция `as.Date()`. В данном случае точка — это лишь часть названия функции, а не какой-то особый оператор. В качестве аргумента функции необходимо задать дату, записанную в виде строки. Запишем дату рождения автора (можете заменить ее на свою):
```{r, collapse=TRUE}
birth = as.Date('1986/02/18')
birth
```
Сегодняшнюю дату вы можете узнать с помощью специальной функции `Sys.Date()`:
```{r, collapse=TRUE}
current = Sys.Date()
current
```
Даты можно вычитать. Результатом выполнения. Например, узнать продолжительность жизни в днях можно так:
```{r, collapse=TRUE}
livedays = current - birth
livedays
```
Вы также можете прибавить к текущей дате некоторое значение. Например, необходимо узнать, какая дата будет через 40 дней:
```{r, collapse=TRUE}
current + 40
```
Имея дату, вы можете легко извлечь из нее день, месяц и год. Существуют специальные функции для этих целей (описанные в главе 8), но прямо сейчас вы можете сделать это сначала преобразовав дату в строку, а затем выбрав из нее подстроку, соответствующую требуемой компоненте даты:
```{r}
cdate = as.character(current)
substr(cdate, 1, 4) # Год
substr(cdate, 6, 7) # Месяц
substr(cdate, 9, 10) # День
```
Более подробно о преобразованиях типов, аналогичных функции `as.character()`, используемой в данном примере, рассказано далее в настоящей главе.
### Время и периоды {#times}
### Логические {#booleans}
Логические переменные возникают там, где нужно проверить условие. Переменная логического типа может принимать значение `TRUE` (истина) или `FALSE` (ложь). Для их обозначения также возможны более компактные константы `T` и `F` соответственно.
Следующие операторы приводят к возникновению логических переменных:
* _РАВНО_ (`==`) --- проверка равенства операндов
* _НЕ РАВНО_ (`!=`) --- проверка неравенства операндов
* _МЕНЬШЕ_ (`<`) --- первый аргумент меньше второго
* _МЕНЬШЕ ИЛИ РАВНО_ (`<=`) --- первый аргумент меньше или равен второму
* _БОЛЬШЕ_ (`>`) --- первый аргумент больше второго
* _БОЛЬШЕ ИЛИ РАВНО_ (`>=`) --- первый аргумент больше или равен второму
Посмотрим, как они работают:
```{r, collapse=TRUE}
a = 1
b = 2
a == b
a != b
a > b
a < b
```
Если необходимо проверить несколько условий одновременно, их можно комбинировать с помощью логических операторов. Наиболее популярные среди них:
* _И_ (`&&`) - проверка истинности обоих условий
* _ИЛИ_ (`||`) - проверка истинности хотя бы одного из условий
* _НЕ_ (`!`) - отрицание операнда (истина меняется на ложь, ложь на истину)
```{r, collapse=TRUE}
c = 3
(b > a) && (c > b)
(a > b) && (c > b)
(a > b) || (c > b)
!(a > b)
```
Более подробно работу с логическими переменными мы разберем далее при знакомстве с условным оператором `if`.
## Манипуляции с типами {#manipulations}
### Определение типа данных {#determine_data_type}
Определение типа данных осуществляется с помощью функции `class()` (см. раздел _Диагностические функции_ во Введении)
```{r, collapse=T}
class(1)
class(0.5)
class(1 + 2i)
class("sample")
class(TRUE)
class(as.Date('1986-02-18'))
```
В вышеприведенном примере видно, что R по умолчанию "повышает" ранг целочисленных данных до более общего типа чисел с плавающей точкой, тем самым закладываясь на возможность точного деления без остатка. Если вы хотите, чтобы данные в явном виде интерпретировались как целочисленные, их нужно принудительно привести к этому типу. Операторы преобразования типов рассмотрены ниже.
### Преобразование типов данных {#conversion}
Преобразование типов данных осуществляется с помощью функций семейства `as(d, type)`, где `d` --- это входная переменная, а `type` --- название типа данных, к которому эти данные надо преобразовать (см. таблицу в начале главы). Несколько примеров:
```{r, echo = FALSE, purl=FALSE}
library(methods)
```
```{r, collapse=T}
k = 1
print(k)
class(k)
l = as(k, "integer")
print(l)
class(l)
m = as(l, "character")
print(m)
class(m)
n = as(m, "numeric")
print(n)
class(n)
```
Для функции `as()` существуют обертки (_wrappers_), которые позволяют записывать такие преобразования более компактно и выглядят как `as.<dataype>(d)`, где `datatype` --- название типа данных:
```{r, collapse=T}
k = 1
l = as.integer(k)
print(l)
class(l)
m = as.character(l)
print(m)
class(m)
n = as.numeric(m)
print(n)
class(n)
d = as.Date('1986-02-18')
print(d)
class(d)
```
Если преобразовать число c плавающей точкой до целого, то дробная часть будет отброшена:
```{r, collapse=T}
as.integer(2.7)
```
После преобразования типа данных, разумеется, к переменной будут применимы только те функции, которые определены для данного типа данных:
```{r, collapse=T, error=T}
a = 2.5
b = as.character(a)
b + 2
nchar(b)
```
### Проверка типов данных и пустых значений {#check_types}
Для проверки типа данных можно использовать функции семейства `is.<datatype>`:
```{r, collapse=T}
is.integer(2.7)
is.numeric(2.7)
is.character('Привет!')
```
Особое значение имеют функции проверки пустых переменных (имеющих значение `NA` - not available), которые могут получаться в результате несовместимых преобразований или соответствовать пропускам в исходных данных:
```{r, collapse=T}
as.integer('Привет!')
is.na(as.integer('Привет!'))
```
## Ввод и вывод данных в консоли {#read_write_console}
### Ввод данных {#read_console}
Для ввода данных через консоль можно воспользоваться функцией `readline()`, которая будет ожидать пользовательский ввод и нажатие клавиши <kbd>Enter</kbd>, после чего вернет введенные данные в виде строки. Предположим, пользователь вызывает эту функцию и вводит с клавиатуры `1024`:
```{r, echo=FALSE, purl=FALSE}
a = "1024"
```
```{r, eval=F}
a = readline()
```
Выведем результат на экран:
```{r, collapse=TRUE}
a
```
> Функция `readline()` всегда возвращает строку, поэтому если вы ожидаете ввод числа, полученное значение необходимо явным образом преобразовать к числовому типу.
Весьма полезной особенностью `readline()` является возможность указания строки запроса (чтобы пользователь понимал, что от него хотят). Строку запроса можно указать при вызове функции:
```{r, eval=F}
lat = readline('Введите широту точки:')
## Введите широту точки:
## 54
lat
## [1] "54"
```
### Вывод данных {#write_console}
Для вывода данных в консоль можно воспользоваться тремя способами:
- Просто напечатать название переменной с новой строки (_не работает при запуске программы командой `Source`_)
- Вызвать функцию `print()`
- Вызвать функцию `cat()`
- Заключить выражение в круглые скобки `()`
Первый способ мы уже регулярно использовали ранее в настоящей главе. Следует обратить внимание на то, что он хорош для отладки программы, но выглядит некрасиво в рабочих программах, поскольку просто печатая название переменной с новой строки вы как бы явно не говорите о том, что хотите вывести ее значение в консоль, а лишь подразумеваете это. Более того, если скрипт запускается командой `Source`, данный метод вывода переменной просто не сработает, интерпретатор его проигнорирует.
Поэтому после отладки следует убрать из программы все лишние выводы в консоль, а оставшиеся (действительно нужные) оформить с помощью функций `print()` или `cat()`.
Функция `print()` работает точно так же, как и просто название переменной с новой строки, отличаясь лишь двумя особенностями:
- `print()` явным образом говорит о том, что вы хотите вывести в консоль некую информацию
- `print()` работает при любых методах запуска программы, в том числе методом `Source`.
Например:
```{r, collapse=TRUE}
a = 1024
a
print(a)
b = "Fourty winks in progress"
b
print(b)
print(paste("2 в степени 10 равно", 2^10))
print(paste("Сегодняшняя дата - ", Sys.Date()))
```
Функция `cat()` отличается от `print()` следующими особенностями:
- `cat()` выводит значение переменной, и не печатает ее измерения и внешние атрибуты типа двойных кавычек вокруг строки. Это означает, что `cat()` можно использовать и для записи данных в файл (на практике этим мало кто пользуется, но знать такую возможность надо).
- `cat()` принимает множество аргументов и может осуществлять конкатенацию строк аналогично функции paste()
- `cat()` не возвращает никакого значений, в то время как `print()` возвращает значение, переданное ей в качестве аргумента.
- `cat()` можно использовать только для атомарных типов данных. Для классов (таких как Date) она будет выводит содержимое объекта, которое может не совпадать с тем, что пользователь ожидает вывести
Например:
```{r, collapse=TRUE}
cat(a)
cat(b)
cat("2 в степени 10 равно", 2^10)
cat("Сегодняшнаяя дата -", Sys.Date())
```
Можно видеть, что в последнем случае `cat()` напечатала отнюдь не дату в ее привычном представлении, а некое число, которое является внутренним представлением даты в типе данных `Date`. Такие типы данных являются классами объектов в R, и у них есть своя функция `print()`, которая и выдает содержимое объекта в виде, который ожидается пользователем. Поэтому пользоваться функцией `cat()` надо с некоторой осторожностью.
Заключительная возможность — вывод с помощью заключения выражения в круглые скобки — очень удобна на стадии отладки программы. При этом переменная, которая создается в выражении, остается доступной в программе:
```{r}
(a = rnorm(5)) # сгенерируем 5 случайных чисел, запишем их в переменную a и выведем на экран
(b = 2 * a) # переменная a доступна, ее можно использовать и далее для вычислений
```
## Условный оператор {#ifelse}
Проверка условий позволяет осуществлять так называемое ветвление в программе. Ветвление означает, что при определенных условиях (значениях переменных) будет выполнен один программный код, а при других условиях --- другой. В R для проверки условий используется условный оператор __if --- else if --- else__ следующего вида:
```
if (condition) {
statement1
} else if (condition) {
statement2
} else {
statement3
}
```
Сначала проверяется условие в выражении `if (condition)`, и если оно истинно, то выполнится вложенный в фигурные скобки программный код `statement1`, после чего оставшиеся условия не будут проверяться. Если первое условие ложно, программа перейдет к проверке следующего условия `else if (condition)`. Далее, если оно истинно, то выполнится вложенный код `statement2`, если нет --- проверка переключится на следующее условие и так далее. Заключительный код `statement3`, следующий за словом `else`, выполнится только если ложными окажутся все предыдущие условия.
> Конструкций `else if` может быть произвольное количество, конструкции `if` и `else` могут встречаться в условном операторе только один раз, в начале и конце соответственно. При этом условный оператор может состоять только из конструкции `if`, а `else if` и `else` не являются обязательными.
Например, сгенерируем случайное число, округлим его до одного знака после запятой и проверим относительно нуля:
```{r}
(a = round(rnorm(1), 1))
if (a < 0) {
cat('Получилось отрицательное число!')
} else if (a > 0) {
cat('Получилось положительное число!')
} else {
cat('Получился нуль!')
}
```
Условия можно использовать, в частности, для того чтобы обрабатывать пользовательский ввод в программе. Например, охарактеризуем положение точки относительно Полярного круга:
```{r, eval=F}
phi = as.numeric(readline('Введите широту вашей точки:'))
```
```{r, echo=FALSE, purl=FALSE}
phi = 69
```
Пользователь вводит `68`, а мы оцениваем результат:
```{r}
if (!is.na(phi)) { # проверяем, является ли введенное значение числом
if (abs(phi) >= 66.562 && abs(phi) <= 90) { # выполняем проверку на заполярность
cat('Точка находится в Заполярье')
} else {
cat('Точка не находится в Заполярье')
}
} else {
cat('Необходимо ввести число!') # оповещаем о некорректном вводе
}
```
## Оператор переключения {#switch}
Оператор переключения (switch) является удобной заменой условному оператору в тех случаях, когда надо вычислить значение переменной в зависимости от значения другой переменной, которая может принимать _ограниченное (заранее известное)_ число значений. Например:
```{r, eval=F}
name = readline('Введите название федерального округа:')
```
Пользователь вводит:
```
Приволжский
```
```{r, echo=FALSE, purl=FALSE}
name = "Приволжский"
```
```{r}
# Определим центр в зависимости от названия:
capital = switch(name,
'Центральный' = 'Москва',
'Северо-Западный' = 'Санкт-Петербург',
'Южный' = 'Ростов-на-Дону',
'Северо-Кавказский' = 'Пятигорск',
'Приволжский' = 'Нижний Новгород',
'Уральский' = 'Екатеринбург',
'Сибирский' = 'Новосибирск',
'Дальневосточный' = 'Хабаровск')
print(capital)
```
## Прерывание программы {#stop}
В процессе выполнения программы могут возникнуть ситуации, при которых дальнейшее выполнение программы невозможно или недопустимо. Например, пользователь вместо числа ввёл в консоли букву. Хорошим тоном разработчика в данном случае будет не пускать ситуацию на самотёк и ждать пока программа сама споткнется и выдаст системное сообщение об ошибке, а обработать некорректный ввод сразу, сообщить об этом пользователю и остановить программу явным образом.
Прервать выполнение программы можно разными способами. Рассмотрим две часто используемые для этого функции:
- `stop(...)` выводит на экран объекты, перечисленные через запятую в `...` и завершает выполнение программы. При ручном вызове этой функции в `...` целесообразно передать текстовую строку с сообщением о причине остановки программы. Вызов `stop()` происходит обычно после проверки некоторого условия оператором `if-else`.
- `stopifnot(...)` вызывает `stop()`, если хотя бы одно из выражений, перечисленных через запятую в `...` имеет значение `FALSE`. При этом в `stop()` передается _первое_ выражение, которое было оценено в `FALSE`.
Реализуем вышеописанный пример с контролем пользовательского ввода:
```{r, eval=F}
n = as.numeric(readline('Введите число:'))
stopifnot(is.numeric(n)) # остановим выполнение, если получилось не число
cat(n^2) # возведем в квадрат и выведем на экран, если все ОК
```
Если пользователь введет `abc`, программа остановит выполнение:
```{r, echo=FALSE, purl=FALSE, error=TRUE}
n = "abc"
stopifnot(is.numeric(n))
```
Обратите внимание, что __R__ напечатал также и само выражение, которое было оценено как `FALSE`. Вышеприведенный код можно сделать более дружелюбным для пользователя, если воспользоваться непосредственно функцией `stop()`:
```{r, eval=F}
n = as.numeric(readline('Введите число:'))
if (!is.numeric(n)) stop('Введенная строка не является числом') # остановим выполнение
cat(n^2) # возведем в квадрат и выведем на экран, если все ОК
```
Вывод программы в случае ввода строки `abc` будет следующим:
```{r, echo=FALSE, purl=FALSE, error=TRUE}
n = "abc"
if (!is.numeric(n)) stop('Введенная строка не является числом')
```
## Технические детали {#basics-detail}
Когда вы присваиваете значение переменной другой переменной, копирования не происходит. Оба имени будут ссылаться на один и тот же объект, до тех пор, пока через одно из имен не будет предпринята попытка модифицировать объект. Это можно легко проверить с помощью функции `tracemem()`:
```{r}
a = 1
b = a
cat('a:', tracemem(a), '\n')
cat('b:', tracemem(b), '\n')
a = 2
cat('a:', tracemem(a), '\n') # объект скопирован в другую область памяти
cat('b:', tracemem(b), '\n')
```
> Подобное поведение называется _copy-on-modify_. Оно позволяет экономить на вычислениях в случае, когда копия и оригинал остаются неизменными. Аналогичное правило применяется когда вы копируете структуры данных, такие как векторы, списки и фреймы данных (см. Главу \@ref(data-structures)). Более подробно см. параграф 2.3 в [@wickham:2019].
## Краткий обзор {#review}
Для просмотра презентации щелкните на ней один раз левой кнопкой мыши и листайте, используя кнопки на клавиатуре:
```{r, echo=FALSE}
knitr::include_url('https://tsamsonov.github.io/r-geo-course-slides/01_Basics.html#1', height = '390px')
```
> Презентацию можно открыть в отдельном окне или вкладке браузере. Для этого щелкните по ней правой кнопкой мыши и выберите соответствующую команду.
## Контрольные вопросы и упражнения {#questions_tasks_basics}
### Вопросы {#questions_basics}
1. Какие типы данных поддерживаются в R? Каковы их англоязычные наименования?
1. Что такое переменная?
1. Какой оператор используется для записи значения в переменную?
1. С помощью какой функции можно узнать тип переменной?
1. С помощью какого семейства функций можно преобразовывать типы переменных?
1. Можно ли использовать ранее созданное имя переменной для хранения новых данных другого типа?
1. Можно ли записать в переменную результат выполнения выражения, в котором она сама же и участвует?
1. Какая функция позволяет прочитать пользовательский ввод с клавиатуры в консоли? Какой тип данных будет иметь возвращаемое значение?
1. Какую функцию можно использовать для вывода значения переменной в консоль? Чем отличается использование этой функции от случая, когда вы просто пишете название переменной в строке программы?
1. Какой символ является разделителем целой и дробной части при записи чисел с плавающей точкой?
1. Что такое операторы и операнды? Приведите примеры бинарных и унарных операторов.
1. Какое значение будет имет результат деления на ноль?
1. Какие функции выполняют операторы `%%, %/%, ^, **`?
1. Как проверить, является ли число четным?
1. Как определить количество символов в строке?
1. Как называется операция состыковки нескольких строк и с помощью какой функции она выполняется? Как добиться того, чтобы при этом не добавлялись пробелы между строками?
1. С помощью какой функции можно создать дату из строки?
1. Как извлечь из даты год? Месяц? День?
1. Какая функция позволяет получить дату сегодняшнего дня?
1. Можно ли складывать даты и числа? Если да, то в каких единицах измерения будет выражен результат?
1. Какова краткая форма записи логических значений `TRUE` и `FALSE`?
1. Каким числам соответствуют логические значения `TRUE` и `FALSE`?
1. Сколько операндов должно быть верно, чтобы оператор логического И (`&&`) принял значение `TRUE`? Что можно сказать в этом отношении об операторе ИЛИ (`||`)?
1. Можно ли применять арифметические операции к логическим переменным? Что произойдет, если прибавить или вычесть из числа `a` значение `TRUE`? А если заменить `TRUE` на `FALSE`?
1. Что такое условный оператор и для каких сценариев обработки данных необходимы условные операторы?
1. Перечислите ключевые слова, которые могут быть использованы для организации условных операторов
1. При каких сценариях целесообразно использовать оператор переключения?
### Упражнения {#tasks_basic}
1. Запишите условие проверки неравенства чисел `a` и `b` не менее чем _тремя_ способами.
1. Напишите программу, которая запрашивает в консоли целое число и определяет, является ли оно чётным или нечетным. Программа должна предварительно определить, является ли введенное число а) числом и б) целым числом.
> __Подсказка:__ результат конвертации строки в целое число и число с плавающей точкой отличается. Вы можете использовать это для проверки, является ли введенное число целым.
1. Напишите программу, которая считывает из консоли введенную пользователем строку и выводит в консоль количество символов в этой строке. Вывод оформите следующим образом: `"Длина введенной строки равняется ... символам"`, где вместо многоточия стоит вычисленная длина.
1. В программе в виде переменных задайте координаты населенного пункта _А_ (`x1, y1`), а также дирекционный угол `D` и расстояние `L` до населенного пункта _B_. Напишите код, который определяет координаты населенного пункта _B_ (`x2, y2`).
1. Функция `atan2()` позволяет найти математический азимут (полярный угол), если известны координаты вектора между двумя точками. Используя эту функцию, напишите программу, которая вычисляет географический азимут между точками _А_ (`x1, y1`) и _B_ (`x2, y2`). Координаты точек задайте в виде переменных непосредственно в коде.
> __Математический азимут__ отсчитывается от направления на восток против часовой стрелки. __Географический азимут__ отсчитывается от направления на север по часовой стрелке).
----
_Самсонов Т.Е._ **Визуализация и анализ географических данных на языке R.** М.: Географический факультет МГУ, `r lubridate::year(Sys.Date())`. DOI: [10.5281/zenodo.901911](https://doi.org/10.5281/zenodo.901911)
----