-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy path02-DataStructures.qmd
725 lines (568 loc) · 62.3 KB
/
02-DataStructures.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
# Структуры данных {#data-structures}
[Программный код главы](https://github.com/tsamsonov/r-geo-course/blob/master/code/02-DataStructures.R)
Структура данных --- это программная единица, позволяющая хранить и обрабатывать множество однотипных и/или логически связанных данных. Структуры данных также являются типами данных, но не простыми, а составными. Поэтому обычно, когда говорят "тип данных", подразумевают именно простые типы данных, рассмотренные в предыдущей главе. В __R__ общеупотребительны следующие структуры данных: векторы, матрицы, массивы, фреймы данных, списки и факторы. С использованием структур данных тесно связаны циклы — разновидность управляющей конструкции, предназначенная для многократного повторения определенного набора инструкций.
## Однородные структуры данных {#data_structures_homo}
### Векторы {#vectors}
Вектор представляет собой упорядоченную последовательность объектов одного типа. Вектор может состоять _только_ из чисел, _только_ из строк, _только_ из дат или _только_ из логических значений и т.д. Числовой вектор легко представить себе в виде набора цифр, выстроенных в ряд и пронумерованных согласно порядку их расстановки.
Вектор является простейшей и одновременно базовой структурой данных в __R__. Понимание принципов работы с векторами необходимо для дальнейшего знакомства с более сложными структурами данных, такими как матрицы, массивы, фреймы данных, тибблы, списки и факторы.
#### Создание {#vector_creation}
Существует множество способов создания векторов. Среди них наиболее употребительны:
1. Явное перечисление элементов
2. Создание пустого вектора ("болванки"), состоящего из заданного числа элементов
3. Генерация последовательности значений
4. Генерация случайного множества значений
Для создания вектора путем __перечисления__ элементов используется функция `c()`:
```{r, collapse=TRUE}
# вектор из строк — цвета некоторых веток Московского метро
colors = c("Красная", "Зеленая", "Синяя", "Коричневая", "Оранжевая")
colors
```
```{r, collapse=TRUE}
# вектор из чисел — длина веток в километрах (в той же последовательности)
lengths = c(28, 40, 45, 19, 38)
lengths
```
```{r, collapse=TRUE}
# вектор из булевых переменных — наличие открытых наземных участков (в той же последовательности)
opens = c(FALSE, TRUE, TRUE, FALSE, FALSE)
opens
```
> Внимание: не используйте латинскую букву 'c' в качестве названия переменной! Это приведет к конфликту названия встроенной функции c() и определенной вами переменной
Помимо этого, распространены сценарии, когда вам нужно создать вектор, но заполнять его значениями вы будете по ходу выполнения программы --- скажем, при последовательной обработке строк таблицы. В этом случае вам известно только предполагаемое количество элементов вектора и их тип. Здесь лучше всего подойдет __создание пустого вектора__, которое выполняется функцией `vector()`. Функция принимает 2 параметра:
* `mode` отвечает за тип данных и может принимать значения равные `"logical"`, `"integer"`, `"numeric"` (или `"double"`), `"complex"`, `"character"` и `"raw"`
* `length` отвечает за количество элементов
Например:
```{r, collapse=TRUE}
# Вектор из 5 элементов, который предполагается заполнить целыми числами
intvalues = vector(mode = "integer", length = 5)
intvalues # по умолчанию заполнен нулями
# Вектор из 10 элементов, который предполагается заполнить символьными данными (строками)
charvalues = vector("character", 10)
charvalues # по умолчанию заполнен пустыми строками
```
Обратите внимание на то, что в первом случае подстановка параметров произведена в виде `параметр = значение`, а во втором указаны только значения. В данном примере оба способа эквивалентны. Однако первый способ безопаснее и понятнее. Если вы указываете только значения параметров, нужно помнить, что интерпретатор будет подставлять их именно в том порядке, в котором они перечислены в описании функции.
> Описание функции можно посмотреть, набрав ее название в консоли ее название со знаком вопроса в качестве префикса. Например, для вышеуказанной функции надо набрать `?vector`
Наконец, третий распространенный способ создания векторов --- это __генерация последовательности__. Чтобы сформировать вектор из натуральных чисел от `M` до `N`, можно воспользоваться специальной конструкцией: `M:N`:
```{r, collapse=TRUE}
index = 1:5 # эквивалентно c(1,2,3,4,5)
index
index = 2:4 # эквивалентно c(2,3,4)
index
```
Существует и более общий способ создания последовательности --- функция `seq()`, которая позволяет генерировать вектора значений нужной длины и/или с нужным шагом:
```{r, collapse=TRUE}
seq(from = 1, by = 2, length.out = 10) # 10 нечетных чисел, начиная с единицы
seq(from = 2, to = 20, by = 3) # от 2 до 20 с шагом 3 (сколько поместится)
seq(length.out = 10, to = 2, by = -2) # убывающая последовательность из 10 четных чисел с последним элементом, равным 2
```
Как видно, параметры функции `seq()` можно комбинировать различными способами и указывать в произвольном порядке (при условии, что вы используете полную форму (`параметр = значение`). Главное, чтобы их совокупность _однозначно описывала последовательность_. Хотя, скажем, последний пример убывающей последовательности нельзя признать удачным с точки зрения наглядности.
Аналогичным образом можно создавать _последовательности дат_:
```{r, collapse=TRUE}
seq(from = as.Date('2016/09/01'), by = 1, length.out = 7) # Даты первой недели учебного 2016/2017 года
seq(from = Sys.Date(), by = 7, length.out = 5) # Пять дат через неделю, начиная с сегодняшнего дня
```
Часто оказывается полезным такая функция как генерация множества случайных значений, подчиненных определенному закону распределения. Наиболее часто испольщуются функции `runif()` (равномерное распределение) и `rnorm()` (нормальное распределение):
```{r}
runif(5, 0, 100) # 5 чисел равномерного распределения в диапазоне от 0 до 100
rnorm(5, 10, 5) # 5 чисел нормального распределения со средним = 10 и СКО = 5
```
#### Индексирование {#vector_indexing}
К отдельным __элементам вектора__ можно обращаться по их индексам:
```{r, collapse=TRUE}
colors[1] # первый элемент вектора
colors[3] # третий элемент
```
> ВНИМАНИЕ: элементы векторов и других структур данных в языке R индексируются от 1 до N, где N — это длина вектора. Это отличает R от широко распространенных Си-подобных языков программирования (C, C++, C#, Java, Python), в которых индексы элементов начинаются с 0 и заканчиваются N-1. Например, первый элемент списка (аналог вектора в R) на языке Python извлекался бы как colors[0]. Будьте внимательны, особенно если программируете на нескольких языках.
__Количество элементов (длину) вектора__ можно узнать с помощью функции `length()`:
```{r, collapse=TRUE}
length(colors)
```
Последний элемент вектора можно извлечь, если мы знаем его длину:
```{r, collapse=TRUE}
n = length(colors)
colors[n]
```
Последовательности удобно использовать для извлечения подвекторов. Предположим, нужно извлечь первые 4 элемента. Для этого запишем:
```{r, collapse=TRUE}
lengths[1:4]
```
Индексирующий вектор можно создать заранее. Это удобно, если номера могут меняться в программе:
```{r, collapse=TRUE}
m = 1
n = 4
index = m:n
lengths[index]
```
Обратите внимание на то что по сути один вектор используется для извлечения элементов из другого вектора. Это означает, что мы можем использовать не только простые последовательности натуральных чисел, но и векторы из прозвольных индексов. Например:
```{r, collapse=TRUE}
index = c(1, 3, 4) # хотим извлечь 1, 3 и 4 элемент списка
lengths[index]
index = c(5, 1, 4, 2) # индексы могут располагаться в произвольном порядке
lengths[index]
```
#### Преобразование {#vector_transform}
К числовым векторам можно применять множество функций. Прежде всего, нужно знать функции вычисления базовых параметров статистического ряда --- минимум, максимум, среднее, медиана, дисперсия, размах вариации, среднеквадратическое отклонение, сумма:
```{r, collapse=TRUE}
min(lengths) # минимум
max(lengths) # максимум
range(lengths) # размах вариации = максимум - минимум
mean(lengths) # среднее арифметическое
median(lengths) # медиана
var(lengths) # дисперсия (по английски - вариация, variation)
sd(lengths) # среднеквадратическое отклонение (standard deviation)
sum(lengths) # сумма
```
Одной из мощнейших особенностей __R__ является то что он не проводит различий между числами и векторами чисел. Поскольку R является матричным языком, каждое число представляется как вектор длиной 1 (или матрица $1х1$). Это означает, что любая математическая функция, применимая к числу, будет применима и к вектору:
```{r, collapse=TRUE}
lengths * 1000 # преобразуем длины линий в метры
sqrt(lengths) # квадратный корень из длины каждого элемента
stations = c(20, 21, 22, 12, 24) # количество станций
dens = stations / lengths # плотность станций по веткам метро = кол-во станций / длина
dens
```
#### Поиск и сортировка {#vector_search_sorting}
К важнейшим преобразованиям векторов относится их __сортировка__:
```{r, collapse=TRUE}
lengths2 = sort(lengths) # сортировка по возрастанию значений
lengths2 # отсортированный вектор
lengths # сравним с исходным
lengths2 = sort(lengths, decreasing = TRUE) # сортировка по убыванию значений. Нужно задать параметр decreasing
lengths2 # отсортированный вектор
lengths # сравним с исходным
```
Другая распространенная задача --- это __поиск индекса__ элемента по его значению. Например, вы хотите узнать, какая ветка Московского метро (среди рассматриваемых) является самой длинной. Вы, конечно, легко найдете ее длину с помощью функции `max(lengths)`. Однако это не поможет вам узнать ее название, поскольку оно находится в другом векторе, и его индекс в массиве неизвестен. Поскольку векторы упорядочены одинаково, нам достаточно узнать, под каким индексом в массиве `lengths` располагается максимальный элемент, и затем извлечь цвет линии метро под тем же самым индексом. Дл поиска индекса элемента используется функция `match()`:
```{r, collapse=TRUE}
l = max(lengths) # находим максимальное значение
idx = match(l, lengths) # находим индекс элемента, равного l, в списке lengths
color = colors[idx] # извлекаем цвет ветки метро
color
```
Здесь непохо бы лишний раз потренироваться в конкатенации строк, чтобы вывести результат красиво!
```{r, collapse=TRUE}
s = paste(color, "ветка Московского метро — самая длинная. Ее протяженность составляет", l, "км")
s
```
Ну и напоследок пример "матрешки"" из функций --- как найти название самой плотной линии одним выражением:
```{r, collapse=TRUE}
colors[match(max(dens),dens)]
```
#### Проверка условий {#vector_conditions}
Проверка условия для вектора приводит к получению вектора логических значений:
```{r}
lengths > 20
```
Такого рода условия используются для фильтрации фреймов данных (см. далее)
Для векторов существует специальная форма векторизованного условного оператора -- функция `ifelse()`. Она позволяет создать вектор, каждый элемент которого вычисляется по-разному в зависимости от значения элемента другого вектора в соответствующей позиции. Например, мы можем охарактеризовать каждую линию метро как длинную или короткую, установив порог в 20 км:
```{r}
(line_type = ifelse(lengths > 20, 'Длинная', 'Короткая'))
```
#### Описательные статистики {#vector_summaries}
Можно получить краткую статистическую сводку по вектору (и любой другой структуре данных) с использованием функции `summary()`. Для качественных переменных выдаются частоты вхождения каждого случая, для количественных — набор основных описательных статистик:
```{r}
summary(lengths)
summary(opens)
```
### Матрицы {#matrices}
Матрица — это обобщение понятия вектора на 2 измерения. С точки зрения анализа данных матрицы ближе к реальным данным, посколько каждая матрица по сути представляет собой таблицу со столбцами и строками. Однако матрица, как и вектор, может содержать только элементы одного типа (числовые, строковые, логические и т.д.). Позже мы познакомимся с фреймами данных, которые не обладают подобным ограничением.
Матрица, как правило, создается с помощью функции `matrix`, которая принимает 3 обязательных аргумента: вектор исходных значений, количество строк и количество столбцов:
```{r, collapse=TRUE}
v = 1:12 # создадим вектор из натуральных чисел от 1 до 12
m = matrix(v, nrow = 3, ncol = 4)
m
```
По умолчанию матрица заполняется данными вектора по столбцам, что можно видеть в выводе программы. Если вы хотите заполнить ее по строкам, необходимо указать параметр `byrow = TRUE`:
```{r, collapse=TRUE}
m = matrix(v, nrow = 3, ncol = 4, byrow = TRUE)
m
```
Доступ к элементам матрицы осуществляется аналогично вектору, за исключением того что нужно указать положение ячейки в строке и столбце:
```{r, collapse=TRUE}
m[2,4] # 2 строка, 4 толбец
m[3,1] # 3 строка, 1 столбец
```
Помимо этого, из матрицы можно легко извлечь одну строку или один столбец. Для этого достаточно указать только номер строки или столбца, а номер второго измерения пропустить до или после запятой. Результат является вектором:
```{r, collapse=TRUE}
m[2,] # 2 строка
m[,3] # 3 cтолбец
```
К матрицам можно применять операции, аналогичные операциям над векторами:
```{r, collapse=TRUE}
log(m) # натуральный логарифм ото всех элементов
sum(m) # сумма всех элементов матрицы
median(m) # медиана
```
B и получать по ним описательные статистики:
```{r}
summary(m)
```
А вот сортировка матрицы приведет к тому что будет возвращен обычный вектор:
```{r, collapse=TRUE}
sort(m)
```
К матрицам также применимы специальные функции, известные из линейной алгебры, такие как транспонирование и вычисление определителя:
```{r, collapse=TRUE, error = TRUE}
t(m) # транспонированная матрица
m2=matrix(-3:3,nrow = 3, ncol = 3)
m2
det(m2) # определитель матрицы
det(m) # ошибка! определитель вычисляется только для квадратных матриц
```
Матрицы также можно перемножать с помощью специального оператора `%*%`. При этом, как мы помним, число столбцов в первой матрице должно равняться числу строк во второй:
```{r, collapse=TRUE, error = TRUE}
m2 %*% m
m %*% m2 # ошибка!
```
Функция `match()`, которую мы использовали для поиска элементов в векторе, не работает для матриц. Вместо этого необходимо использовать функцию `which()`. Если мы хотим найти в матрице m позицию числа $8$, то вызов функции будет выглядеть так:
```{r, collapse=TRUE}
which(m == 8, arr.ind = TRUE)
```
В данном случае видно, что результат возвращен в виде матрицы $1 \times 2$. Обратите внимание на то, что колонки матрицы имеют названия. Попробуем использовать найденные индексы, чтобы извлечь искомый элемент:
```{r, collapse=TRUE}
indexes = which(m == 8, arr.ind = TRUE)
row = indexes[1,1]
col = indexes[1,2]
m[row,col]
```
Ура! Найденный элемент действительно равен $8$.
Еще один полезный способ создания матрицы — это собрать ее из нескольких векторов, объединив их по строкам. Для этого можно использовать функции `cbind()` и `rbind()`. На предыдущем занятии мы создали векторы с длиной и количеством станций на разных ветках метро. Можно объединить их в одну матрицу:
```{r, collapse=TRUE}
lengths = c(28, 40, 45, 19, 38)
stations = c(20, 21, 22, 12, 24)
cbind(lengths, stations) # соединим вектора в качестве столбцов
rbind(lengths, stations) # соединим вектора в качестве строк
```
Cтроки и столбцы матрицы можно использовать как векторы при выполнении арифметических операций:
```{r, collapse=TRUE}
mm = cbind(lengths, stations)
mm[,2]/mm[,1] # количество станций на 1 км пути
```
Результат можно присоединить к уже созданной матрице:
```{r, collapse=TRUE}
dens = mm[,2]/mm[,1]
mm=cbind(mm, dens)
mm
```
Содержимое матрицы можно просмотреть в более привычном табличном виде для этого откройте вкладку _Environment_ и щелкните на строку с матрицей в разделе _Data_
Матрицы, однако, не дотягивают по функциональности до представления таблиц, и не предназначены для объединения разнородных данных в один набор (как мы это сделали). Если вы присоедините к матрице столбец с названиями веток метро, система не выдаст сообщение об ошибке, но преобразует матрицу в текстовую, так как текстовый тип данных способен представить любой другой тип данных:
```{r, collapse=TRUE}
colors = c("Красная", "Зеленая", "Синяя", "Коричневая", "Оранжевая")
mm2=cbind(mm,colors)
mm2 # обратите внимание на то, что вокруг чисел добавились кавычки
```
При попытке выполнить арифметическое выражение над прежде числовыми полями, вы получите сообщение об ошибке:
```{r, collapse=TRUE,error = TRUE}
mm2[,2]/mm2[,1]
```
### Массивы {#arrays}
_Массивы (arrays)_ --- это многомерные структуры данных, с колчеством измерений 3 и более. Трехмерный массив представляет собой куб однородных данных. Для создания массива используется функция `array()`:
```{r, collapse=TRUE}
z = array(1:36, c(3,4,2)) # вектор значений для заполнения массива, а также длина каждого измерения
print(z)
```
Массивы возникают тогда, например, когда имеются многомерные данные, зафиксированные по регулярной сетке географичесих локаций (это типично для геофизических данных). При этом 2 измерения отвечают за местоположение, а третье измерение — за временной срез или показатель.
## Разнородные структуры данных {#heterogeneous}
### Фреймы данных {#data_frames}
_Фреймы данных_ — это обобщение понятия матрицы на данные смешанных типов. Фреймы данных - наиболее распространенный формат представления табличных данных. Для краткости мы иногда будем называть их просто фреймами.
> Мы специально не используем для перевода слова `data.frame` термин 'таблица', поскольку таблица — это достаточно общая категория, которая описывает концептуальный способ упорядочивания данных. В том же языке R для представления таблиц могут быть использованы как минимум две структуры данных: фрейм данных (data.frame) и тиббл (tibble), доступный в соответствующем [пакете](https://cran.r-project.org/web/packages/tibble/index.html). Мы не будем использовать тибблы в настоящем курсе, но после его освоения вы вполне сможете ознакомиться с ними самостоятельною
Для создания фреймов данных используется функция `data.frame()`:
```{r, collapse=TRUE}
df = data.frame(colors,lengths,stations)
df # как мы видим, уже никаких кавычек вокруг чисел
```
К фреймам также можно пристыковывать новые столбцы:
```{r, collapse=TRUE}
df = cbind(df, dens)
df
```
Когда фрейм данных формируется посредством функции `data.frame()` и `cbind()`, названия столбцов берутся из названий векторов. Обратите внимание на то, что листинге выше столбцы имеют заголовки, а строки — номера.
Как и прежде, к столбцам и строкам можно обращаться по индексам:
```{r, collapse=TRUE}
df[2,2]
df[,3]
df[4,]
```
Вы можете обращаться к отдельным столбцам фрейма данных по их названию, используя оператор `$` (доллар):
```{r, collapse=TRUE}
df$lengths
df$stations
```
Так же как и ранее, можно выполнять различные операции над столбцами:
```{r, collapse=TRUE}
max(df$stations)
df$lengths / df$stations
```
Названия столбцов можно получить с помощью функции `colnames()`
```{r, collapse=TRUE}
colnames(df)
```
Чтобы присоединить строку, сначала можно создать фрейм данных из одной строки:
```{r, collapse=TRUE}
row = data.frame("Фиолетовая", 40.5, 22, 22/45)
```
Далее нужно убедиться, что столбцы в этом мини-фрейме называются также как и в том фрейме, куда мы хотим присоединить строку. Для этого нужно перезаписать результат, возвращаемый функцией `colnames()`:
```{r, collapse=TRUE}
colnames(row) = colnames(df)
```
Обратите внимание на синтаксис вышеприведенного выражения. Когда функция возвращает результат, она обнаруживает свойство самого объекта, и мы можем его перезаписать. После того как столбцы приведены в соответствие, можно присоединить новую строку:
```{r, collapse=TRUE}
df = rbind(df,row)
```
Чтобы _отсортировать_ фрейм данных по значению определенного поля, необходимо узнать порядок элементов в этом поле с помощью функции `order()` и проиндексировать им первое измерение фрейма:
```{r}
df[order(df$lengths), ]
```
Чтобы _отфильтровать_ фрейм данных по значению определенного поля, необходимо передать условие в первое измерение фрейма:
```{r}
df[df$lengths > 40, ]
```
Поскольку названия столбцов хранятся как вектор из строк, мы можем их переделать:
```{r, collapse=TRUE}
colnames(df) = c("Цвет","Длина","Станции","Плотность")
colnames(df)
```
Обратимся по новому названию столбца:
```{r, collapse=TRUE}
df$Длина
```
К фреймам данных, так же как и к однородным структурам, можно применять функцию `summary()` для получения описательных статистик. При этом отчет формируется по каждому столбцу:
```{r}
summary(df)
```
### Списки {#lists}
Список — это наиболее общий тип контейнера в R. Список отличается от вектора тем, что он может содержать набор объектов произвольного типа. В качестве элементов списка могут быть числа, строки, вектора, матрицы, фреймы данных — и все это в одном контейнере. Списки используются чтобы комбинировать разрозненную информацию. Результатом выполнения многих функций является список.
Например, можно создать список из текстового описания фрейма данных, самого фрейма данных и обобщающей статистики по нему:
```{r, collapse=TRUE}
d = "Этот фрейм данных содержит данные по 6 линиям Московского метро"
s = summary(df) # summary() выдает обобщающую статистику вектору, матрице или фрейму данных
```
Сооружаем список из трех элементов:
```{r, collapse=TRUE}
metrolist = list(d, df, s)
metrolist
```
Можно дать элементам списка осмысленные названия при создании:
```{r, collapse=TRUE}
metrolist = list(desc = d, table = df, summary = s)
metrolist
```
Теперь можно обратиться к элементу списка по его названию:
```{r, collapse=TRUE}
metrolist$summary
```
Поскольку `summary` сама является фреймом данных, из нее можно извлечь столбец:
```{r, collapse=TRUE}
metrolist$summary[,3]
```
К элементу списка можно также обратиться по его порядковому номеру или названию, заключив их в _двойные_ квадратные скобки:
```{r, collapse=TRUE}
metrolist[[1]]
metrolist[["desc"]]
```
Использование _двойных скобок_ отличает списки от векторов.
Вызов функции `summary()` в приложении к списку выведет статистику по типам и количеству элементов списка:
```{r}
summary(metrolist)
```
## Факторы {#factors}
Понятие _фактора_ в терминологии __R__ используется для обозначения категориальной (качественной) переменной. Как известно, такие переменные могут быть номинальными (с неопределенным порядком) и порядковыми (с заданным отношением порядка). Проблема взаимодействия с категориальными переменными заключается в том, что они могут приобретать разнообразные формы: быть выражены в виде чисел и строк. Эта форма может быть обманчивой. Например, модели самолетов Boeing и Sukhoi SuperJet обознаются числами (747, 100 и т.д.). Однако очевидно, что складывать и вычитать такие числа смысла нет, они являются формой представления номинальной переменной. Другой пример: названия месяцев записываются в виде строк. Если попытаться отсортировать месяцы цветения различных видов деревьев, то получится бессмысленный алфавитный порядок, в котором апрель следует за августом. В данном случае проблема заключается в том, что мы имеем дело с категориальной переменной, в которой задан порядок следования допустимых значений.
В географических данных категориальные переменные тоже достаточно распространены. К номинальной шкале измерений относятся всевозможные числовые коды: почтовые, ОКАТО и т.д. К порядковой шкале - административный статус населенного пункта, сила землетрясения по шкале Рихтера. Для того, чтобы соответствующие данные в среде __R__ правильно обрабатывались статистическими функциями и отображались в виде подходящих графических способов, необходимо явным образом проинформировать об этом программу. Для этого и создаются факторы.
Фактор построен по принципу [ассоциативного массива](https://ru.wikipedia.org/wiki/Ассоциативный_массив) и является надстройкой над вектором, в которой каждому значению вектора присваивается некий код. Вы можете управлять этими кодами, а можете оставить их на усмотрение программы.
Например, каждая линия Московского метро имеет свой номер. Создадим небольшей фрейм данных с электродепо по интересующим нас веткам метро и рассчитаем по ним описательные статистики:
```{r}
depots = data.frame(
depot = c('Северное', 'Черкизово', 'Сокол', 'Замоскворецкое',
'Братеево', 'Измайлово', 'Фили', 'Митино',
'Красная Пресня', 'Калужское', 'Свиблово'),
year_opened = c(1935, 1990, 1938, 1969,
2014, 1950, 1962, 2016,
1954, 1962, 1978),
line_number = c(1, 1, 2, 2, 2, 3, 3, 3, 5, 6, 6)
)
print(depots)
summary(depots)
```
Как видно, __R__ посчитал нам средний номер линии метро - 3.091, что выглядит, мягко говоря, странновато. Чтобы этого не происходило, укажем в явном виде с помощью функции `factor()`, что номер линии метров является номинальной переменной:
```{r}
depots$line_number = as.factor(depots$line_number)
print(depots$line_number)
```
Мы видим, что у переменной появился дополнительный атрибут `Levels`, который отвечает за список уникальных значений номинальной переменной. Отношение порядка мы здесь не вводим, поскольку номер является условным обозначением.
Попробуем теперь посчитать описательные статистики по переменной и таблице в целом:
```{r}
mean(depots$line_number)
summary(depots)
```
Теперь мы видим, что вместо стандартных статистик __R__ для переменной *line_number* выдает таблицу частот, из которой ясно, что на первой линии два депо, на второй линии три депо и так далее.
## Описание структуры данных {#vector_desc}
Для описания структуры данных можно использовать две широко используемые диагностические функции: `class()` выведет тип структуры, а `str()` выведет детальную выписку по компонентам этой структуры:
```{r}
class(depots) # тип объекта
str(depots) # структура объекта
```
## Циклы {#cycles}
Цикл --- это разновидность управляющей конструкции, предназначенная для организации многократного исполнения набора инструкций. В __R__ циклы наиболее часто используются для пакетной обработки данных, ввода и вывода. Типичными примерами использования циклов являются чтение множества файлов входных данных, а также построение серий графиков и карт одного типа по различным данным. При этом обработка множества строк таблиц в R обычно организуется не средствами циклов, а средствами функций семейства [`lapply`](http://stat.ethz.ch/R-manual/R-devel/library/base/html/lapply.html), о которых мы поговорим в главе, посвященной [техникам программирования](#techniques) на R.
Циклы обычно связаны с проходом по элементам списка/вектора либо с созданием такого списка/вектора. Поэтому они излагаются в настоящей главе.
В R, как и во многих других языках программирования, существует несколько вариантов циклов. Первый вид циклов — это конструкция __for__ с синтаксисом `for (x in X) statement`. Она означает, что:
- переменная `x` должна пробежать по всем элементам последовательности `X`. В качестве последовательности может выступать любой вектор или список.
- каждый раз, когда `x` будет присвоено значение очередного элемента из `X`, будет выполнено выражение `statement`, которое называют _телом цикла_. Соответственно, цикл выполнится столько раз, сколько элементов содержится в последовательности `X`.
> Выполнение тела цикла на каждом проходе называют _итерацией_.
Например, с помощью цикла можно вывести на экран числа от 1 до 10, по одному с каждой строки:
```{r, collapse = TRUE}
## ЦИКЛЫ
for (i in 1:10) print(i)
```
Если тело цикла содержит более одной инструкции R, оно должно быть заключено в фигурные скобки, иначе выполнится только первое выражение, а оставшиеся будут запущены один раз после выхода из цикла:
```{r, collapse = TRUE}
for (i in 1:10) {
a = factorial(i) # факториал i
b = exp(i) # e в степени i
print(a/b) # факториал растет быстрее экспоненты
}
```
Другой вариант цикла организуется с помощью конструкции `while`, имеющей синтаксис `while (condition) statement`. Такая конструкция означает, что тело цикла будет выполняться, пока значение выражения `condition` (условия) равно `TRUE`. Как правило, в теле цикла обновляется некоторая переменная, которая участвует в проверке условия, и предполагается, что рано или поздно оно станет равным `FALSE`, что приведет к выходу из цикла. Например, вышеприведенный цикл, печатающий числа от 1 до 10, можно переписать на `while` следуюшим образом:
```{r, collapse = TRUE}
i = 0
while(i < 10) {
i = i+1
print(i)
}
```
Обратите внимание на то, что мы внутри цикла обновляем значение переменной i.
> Увеличение значения переменной цикла называется _инкрементом_, а уменьшение --- _декрементом_.
Одной из самых распространенных ошибок программистов (особенно начинающих, но и професионалы ее не избегают) является забытая инструкция инкремента (или декремента) переменной цикла, в результате чего цикл становится бесконечным. В этом плане конструкция `for` более надежна.
В качестве примера приведем проход по столбцам фрейма данных и вычисление медианного значения для каждого столбца таблицы линий метро:
```{r}
n = ncol(df)
medians = vector('numeric', n)
for (i in 1:n) {
if(is.numeric(df[, i])){
medians[i] = median(df[, i])
} else {
medians[i] = NA
}
}
colnames(df) # Переменные
medians # Медианные значения
```
Аналогичным образом можно осуществлять проход в цикле по строкам. Выведем информацию о длине каждой станции:
```{r}
k = nrow(df)
for (i in 1:k) {
cat(df[i, 'colors'], 'ветка метро имеет длину',
df[i, 'lengths'], 'км', fill = TRUE)
}
```
Существуют специальные операторы, позволяющие принудительно прервать текущую итерацию цикла и перейти на следующую, а также выйти из цикла вообще. Они называются `next` и `break`. Они бывают полезны, когда в теле цикла может произойти событие, делающее невозможным (или бессмысленным) его дальнейшее выполнение. Например, мы можем выводить информацию об электродепо, имеющихся на линии метро с введенным пользователем номером, до тех пор, пока он не введет символ `q`. Чтобы цикл был бесконечным, используем специальную форму `while (TRUE)`:
```{r, eval=FALSE}
while (TRUE) {
cat('Введите номер ветки метро:')
input = readline()
if (input == 'q')
break
else {
n = as.numeric(input)
if (!is.na(n))
depots[depots$line_number == n, ]
}
}
```
Оператор `next` используется реже, так как в принципе он взаимозаменяем с конструкцией `if-else`. Он бывет удобен, когда в длинном цикле имеется несколько мест, в которых возможен переход на следующую итерацию. При использовании `next` последующий код нет необходимости табулировать и забирать в скобки. Следующие паттерны идентичны, но вариант с `next` позволяет остаться на том же уровне вложенности:
Паттерн 1:
```{r, eval=FALSE}
while (...) {
if (condition1)
next
... # сюда попадем, только если condition1 == FALSE
if (condition2)
next
... # сюда попадем, только если condition2 == FALSE
}
```
Паттерн 2:
```{r, eval=FALSE}
while (...) {
if (!condition1) {
... # сюда попадем, только если condition1 == FALSE
if (!condition2) {
... # сюда попадем, только если condition2 == FALSE
}
}
}
```
## Технические детали {#structures-detail}
Внутреннюю структуру и размер объекта можно исследовать с помощью пакета [lobstr](https://lobstr.r-lib.org/). Например, посмотрим, как организован в пямяти объект `metrolist`:
```{r}
library(lobstr)
ref(metrolist)
obj_size(metrolist)
```
## Краткий обзор {#review_structures}
Для просмотра презентации щелкните на ней один раз левой кнопкой мыши и листайте, используя кнопки на клавиатуре:
```{r, echo=FALSE}
knitr::include_url('https://tsamsonov.github.io/r-geo-course-slides/02_DataStructures.html#1', height = '390px')
```
> Презентацию можно открыть в отдельном окне или вкладке браузере. Для этого щелкните по ней правой кнопкой мыши и выберите соответствующую команду.
## Контрольные вопросы и упражнения {#questions_tasks_vectors}
### Вопросы {#questions_vectors}
1. На какие две большие группы можно разделить структуры данных в R? Чем он отличаются?
1. Что такое вектор в языке R?
1. Какие способы создания векторов существуют?
1. Можно ли хранить в векторе данные разных типов?
1. Как определить длину вектора?
1. Как извлечь из вектора элемент по его индексу?
1. Как извлечь из вектора множество элементов по их индексам?
1. Как извлечь из вектора последний элемент?
1. С помощью какой функции можно сгенерировать последовательность чисел или дат с заданным шагом?
1. Как сгенерировать последовательность целых чисел с шагом 1, не прибегая к функциям?
1. Можно ли применять к векторам арифметические операторы и математические функции? Что будет результатом их выполнения?
1. С помощью какой функции можно отсортировать вектор? Как изменить порядок сортировки на противоположный?
1. С помощью какой функции можно найти индекс элемента вектора по его значению? Что вернет функция, если этот элемент встречается в векторе несколько раз?
1. Как работает функция `ifelse()` и для чего она используется?
1. Как работает функция `summary()` и для чего она используется?
1. Какая функция позволяет создать матрицу? По строкам или по столбцам заполняется матрица при использовании вектора как источника данных по умолчанию?
1. Как извлечь элемент по его индексам из матрицы, массива, фрейма данных, списка?
1. Как извлечь строку или столбец из матрицы или фрейма данных?
1. С помощью какого специального символа можно обратиться к столбцу фрейма данных по его названию?
1. Как получить или записать названия столбцов фрейма данных?
1. Как получить или записать названия строк фрейма данных?
1. Какая структура данных является результатом сортировки матрицы?
1. Какая функция позволяет осуществить транспонирование матрицы?
1. Какой оператор используется для умножения матриц? Каким критериям должны отвечать перемножаемые матрицы, чтобы эта операция была осуществима?
1. Как добавить новый столбец в фрейм данных? Опишите несколько вариантов.
1. Как добавить новую строку в фрейм данных?
1. Что произойдет, если к целочисленной матрице прибавить столбец, заполненный строками?
1. Какая функция позволяет находить индексы элементов матрицы или фрейма данных по их значениям?
1. Что такое цикл и для каких сценариев обработки данных могут быть полезны циклы?
1. Перечислите несколько способов организации циклов в __R__, необходимые ключевые слова и параметры.
1. Что такое инкремент и декремент?
1. Какое ключевое слово позволяет прервать цикл и выйти из него принудительно?
1. Какое ключевое слово позволяет прекратить текущую итерацию цикла и перейти сразу к новой итерации?
1. Являются ли необходимыми фигурные скобки в случае когда цикл или условный оператор содержит только одно выражение? Что говорит об этом стиль программирования на __R__?
### Упражнения {#tasks_vectors}
1. Создайте вектор `temp`, в котором хранятся значения среднемесячных температур воздуха в городе Санкт-Петербурге (данные можно взять [здесь](https://ru.wikipedia.org/wiki/Климат_Санкт-Петербурга)). Напишите программный код, который вычисляет следующие вектора:
- _количественное изменение_ температуры от месяца к месяцу (в градусах)
- _качественное изменение_ температуры от месяца к месяцу (`'потепление'` или `'похолодание'`);
- номера _зимних_ месяцев (со среднемесячной температурой ниже нуля);
- описательные статистики среднемесячных температур (_summary_);
Выведите исходные и вычисленные данные в консоль (с пояснением что они означают).
> __Подсказка__: для вычисления разностей между элементами вектора используйте функцию [`diff()`](https://www.rdocumentation.org/packages/base/versions/3.6.1/topics/diff).
2. На местности задан прямоугольник с координатами левого нижнего (`x1`, `y1`) и правого верхнего (`x2`, `y2`) угла. Напишите программу, которая размещает внутри этого прямоугольника случайным образом _N_ точек и представляет результат в виде матрицы координат `coords` с двумя столбцами и _N_ строками. Вызовите в конце программы `plot(coords)`, чтобы посмотреть на результат. Координаты можно не вводить, а задать прямо в программе в виде переменных.
> __Подсказка__: координаты случайно размещенных точек имеют равномерное распределение. Вам необходимо сначала сформировать случайные векторы координат `X` и `Y`, и после этого объединить их в матрицу.
3. Высотная поясность на северном склоне Западного Кавказа, [согласно](https://bigenc.ru/geography/text/1877178) Большой Российской энциклопедии устроена следующим образом:
- до 500 м — степь и лесостепь
- до 800 м — низкогорные широколиственные леса (дуб, граб)
- до 1300 м — среднегорные широколиственные леса (бук)
- до 1600 м — смешанные леса (ель, пихта, бук)
- до 2300 м — криволесия (береза, бук, клён)
- до 2500 м — субальпийские и альпийские луга
- до 3300 м — субнивальная зона (фрагментарная растительность)
- выше (условно до 5000 м) — гляциально-нивальная зона
Создайте фрейм данных, включающий три столбца: минимальная высота пояса (`Hmin`), максимальная высота пояса (`Hmax`) и название высотного пояса (`Zone`). Минимальную высоту надо вычислить на основе максимальной, приняв, что для нижнего пояса она условно равна $400~м$.
Напишите программу, которая просит пользователя ввести высоту и возвращает высотный пояс, соответствующую введенной высоте (достаточно вывести строчку фрейма данных).
> __Подсказка__: Организуйте обход строчек фрейма данных с помощью цикла от $1$ до $N$, где $N$ — количество строк. Искомый пояс будет найден, как только введенное значение станет меньше чем `Hmax`. После этого можно вывести результат на экран. Если введенное значение больше максимума в столбце `Hmax` или меньше $400$, программа должна выдавать ошибку.
4. [__advanced__] Решите задачу №3, используя только операции над векторами и поиск элементов, и не используя циклы.
5. [__advanced__] Модифицируйте программу, написанную для решения задачи №2 таким образом, чтобы запретить точкам сближаться более чем на заданное расстояние _k_ (это называется _регулярным распределением_ с расстоянием ингибиции _k_). Сохраните результат в виде _фрейма данных_ `points` со столбцами _X_, _Y_ и _D_, где _D_ -- это расстояние до ближайшей точки. Выведите верхние строчки полученной таблицы в консоль, а также полученные точки с помощью команды `plot(coords$X, coords$Y)`.
> __Подсказка__: вам придется генерировать в цикле по одной точке и проверять условие на каждой итерации до тех пор, пока вы не наберете требуемое количество точек. Задавайте значение _k_ малым по отношению к размерам прямоугольника, чтобы избежать излишне долгого выполнения программы.
----
_Самсонов Т.Е._ **Визуализация и анализ географических данных на языке R.** М.: Географический факультет МГУ, `r lubridate::year(Sys.Date())`. DOI: [10.5281/zenodo.901911](https://doi.org/10.5281/zenodo.901911)
----