-
Notifications
You must be signed in to change notification settings - Fork 7
/
013-vector.qmd
522 lines (347 loc) · 30.9 KB
/
013-vector.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
# Вектор {#sec-vector}
## Понятие atomic вектора в R {#sec-atomic}
Если у вас не было линейной алгебры (или у вас с ней было все плохо), то просто запомните, что **вектор** (***atomic vector*** или просто ***atomic***) -- это набор (столбик) чисел в определенном порядке.
Если вы привыкли из школьного курса физики считать вектора стрелочками, то не спешите возмущаться и паниковать. Представьте стрелочки как точки из нуля координат {0,0} до какой-то точки на координатной плоскости, например, {2,3}:
![](images/013-vector_coord_vector.png)
Вот последние два числа и будем считать вектором. Попытайтесь теперь мысленно стереть координатную плоскость и выбросить стрелочки из головы, оставив только последовательность чисел {2,3}:
![](images/013-vector_coord_vector_blur.png)
На самом деле, мы уже работали с векторами в R, но, возможно, вы об этом даже не догадывались. Дело в том, что в R нет как таковых скалярных (т.е. одиночных) значений, **есть вектора длиной 1**. Такие дела!
Чтобы создать вектор из нескольких значений, нужно воспользоваться функцией `c()`:
```{r}
c(4, 8, 15, 16, 23, 42)
c("Hey", "Hey", "Ho")
c(TRUE, FALSE)
```
::: callout-important
## *Осторожно:* ошибка с кириллической "с"
Одна из самых мерзких и раздражающих причин ошибок в коде -- это использование `с` из кириллицы вместо `c` из латиницы. Видите разницу? И я не вижу. А R видит. И об этом сообщает:
```{r error=TRUE}
с(3, 4, 5)
```
:::
Для создания числовых векторов есть удобный оператор `:`.
```{r}
1:10
5:-3
```
Этот оператор создает вектор от первого числа до второго с шагом 1. Вы не представляете, как часто эта штука нам пригодится... Если же нужно сделать вектор с другим шагом, то есть функция `seq()`:
```{r}
seq(10, 100, by = 10)
```
Кроме того, можно задавать не шаг, а длину вектора. Тогда функция `seq()` сама посчитает шаг:
```{r}
seq(1, 13, length.out = 4)
```
Другая функция -- `rep()` -- позволяет создавать вектора с повторяющимися значениями. Первый аргумент -- значение, которое нужно повторять, а второй аргумент -- сколько раз повторять.
```{r}
rep(1, 5)
```
И первый, и второй аргумент могут быть векторами! Если второй агрумент -- вектор такой же длины, то каждое значение первого вектора будет повторено соответствующее количество раз из второго вектора.
```{r}
rep(1:3, 3)
rep(1:3, c(10, 2, 30))
```
Если нужно повторить каждое значение в векторе одно и то же количество раз, то можно воспользоваться дополнительным параметром `each =`:
```{r}
rep(1:3, each = 5)
```
Еще можно объединять вектора (что мы, по сути, и делали, просто с векторами длиной 1):
```{r}
v1 <- c("Hey", "Ho")
v2 <- c("Let's", "Go!")
c(v1, v2)
```
Очень многие функции в R работают именно с векторами. Например, функции `sum()` (считает сумму значений вектора) и `mean()` (считает среднее арифметическое всех значений в векторе):
```{r}
sum(1:10)
mean(1:10)
```
## Приведение типов {#sec-coercion}
Что будет, если вы объедините два вектора с значениями разных типов? Ошибка?
Мы уже обсуждали, что в обычных векторах (*atomic* векторах) может быть только один тип данных. В некоторых языках программирования при операции с данными разных типов мы бы получили ошибку. А вот в R при несовпадении типов произойдет попытка привести типы к "общему знаменателю", то есть конвертировать данные в более "широкий" тип (а иногда -- более "узкий" тип, если того требует функция).
Например:
```{r}
c(FALSE, 2)
```
`FALSE` превратился в `0` (а `TRUE` превратился бы в `1`), чтобы оба значения можно было объединить в вектор. То же самое произошло бы в случае операций с векторами:
```{r}
2 + TRUE
```
Это называется **неявным приведением типов (implicit coercion)**.
Вот более сложный пример:
```{r}
c(TRUE, 3, "hi")
```
Здесь все значения были приведены сразу к строковому типу данных.
::: callout-caution
## Время мемов
![](images/013-vector_coercion.jpeg){fig-align="center" width="56%"}
:::
У R есть иерархия приведения типов:
`NULL < raw < logical < integer < double < complex < character < list < expression`.
Мы из этого списка еще многого не знаем, сейчас важно запомнить, что логические данные -- `FALSE` и `TRUE` -- превращаются в `0` и `1` соответственно, а `0` и `1` в строчки `"0"` и `"1"`.
Если Вы боитесь полагаться на приведение типов, то можете воспользоваться функциями `as.нужныйтипданных()` для явного приведения типов (**explicit coercion**):
```{r}
as.numeric(c(TRUE, FALSE, FALSE))
as.character(as.numeric(c(TRUE, FALSE, FALSE)))
```
Можно превращать и обратно, например, строковые значения в числовые. Если среди числа встретится буква или другой неподходящий знак, то мы получим предупреждение `NA` -- пропущенное значение (мы очень скоро научимся с ними работать, см. @sec-about).
```{r}
as.numeric(c("1", "2", "три"))
```
::: callout-tip
## *Полезное:* подсчет количества и доли
Один из распространенных примеров использования неявного приведения типов -- использования функций `sum()` и `mean()` для подсчета в логическом векторе количества и доли `TRUE` соответственно. Мы будем много раз пользоваться этим приемом в дальнейшем!
:::
## Векторизация {#sec-vector_op}
Все те арифметические операторы, что мы использовали ранее, можно использовать с векторами одинаковой длины:
```{r}
n <- 1:4
m <- 4:1
n + m
n - m
n * m
n / m
n ^ m + m * (n - m)
```
Если применить операторы на двух векторах одинаковой длины, то мы получим результат поэлементного применения оператора к двум векторам. Это называется **векторизацией** (**vectorization**).
> Если после какого-нибудь MATLAB Вы привыкли, что по умолчанию операторы работают по правилам линейной алгебры и `m * n` будет давать скалярное произведение (*dot product*), то снова нет. Для скалярного произведения нужно использовать операторы с `%` по краям:
```{r}
n %*% m
```
> Абсолютно так же и с операциями с матрицами в R, хотя про матрицы будет немного позже.
В принципе, большинство функций в R, которые работают с отдельными значениями, так же хорошо работают и с целыми векторами. Скажем, если вы хотите извлечь корень из нескольких чисел, то для этого не нужны никакие циклы (как это обычно делается во многих других языках программирования). Можно просто "скормить" вектор функции и получить результат применения функции к каждому элементу вектора:
```{r}
sqrt(1:10)
```
Таких векторизованных функций в R очень много. Многие из них написаны на более низкоуровневых языках программирования (C, C++, FORTRAN), за счет чего использование таких функций приводит не только к более элегантному, лаконичному, но и к более быстрому коду.
> Векторизация в R -- это очень важная фишка, которая отличает этот язык программирования от многих других. Если вы уже имеете опыт программирования на другом языке, то вам во многих задачах захочется использовать циклы типа `for` и `while` \@ref(for). Не спешите этого делать! В очень многих случаях циклы можно заменить векторизацией. Тем не менее, векторизация -- это не единственный способ избавить от циклов типа `for` и `while` \@ref(apply).
## Ресайклинг {#sec-recycling}
Допустим мы хотим совершить какую-нибудь операцию с двумя векторами. Как мы убедились, с этим обычно нет никаких проблем, если они совпадают по длине. А что если вектора не совпадают по длине? Ничего страшного! Здесь будет работать правило **ресайклинга** (*правило переписывания, recycling rule*). Это означает, что если мы делаем операцию на двух векторах разной длины, то если короткий вектор кратен по длине длинному, короткий вектор будет повторяться необходимое количество раз:
```{r}
n <- 1:4
m <- 1:2
n * m
```
А что будет, если совершать операции с вектором и отдельным значением? Можно считать это частным случаем ресайклинга: короткий вектор длиной 1 будет повторятся столько раз, сколько нужно, чтобы он совпадал по длине с длинным:
```{r}
n * 2
```
Если же меньший вектор не кратен большему (например, один из них длиной 3, а другой длиной 4), то R посчитает результат, но выдаст предупреждение.
```{r}
n + c(3,4,5)
```
Проблема в том, что эти предупреждения могут в неожиданный момент стать причиной ошибок. Поэтому [не стоит полагаться](https://stackoverflow.com/questions/6555651/under-what-circumstances-does-r-recycle) на ресайклинг некратных по длине векторов. А вот ресайклинг кратных по длине векторов -- это очень удобная штука, которая используется очень часто.
## Индексирование векторов {#sec-index_atomic}
Итак, мы подошли к одному из самых сложных моментов. И одному из основных. От того, как хорошо вы научись с этим работать, зависит весь ваш дальнейший успех на R-поприще!
Речь пойдет об **индексировании** векторов. Задача, которую Вам придется решать каждые пять минут работы в R -- как выбрать из вектора (или же списка, матрицы и датафрейма) какую-то его часть. Для этого используются квадратные скобочки `[]` (не круглые -- они для функций!).
Самое простое -- индексировать по номеру индекса, т.е. порядку значения в векторе.
```{r}
n <- c(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)
n[1]
n[10]
```
> Если вы знакомы с другими языками программирования (не MATLAB, там все так же) и уже научились думать, что индексация с 0 -- это очень удобно и очень правильно (ну или просто свыклись с этим), то в R вам придется переучиться обратно. Здесь первый индекс -- это 1, а последний равен длине вектора -- ее можно узнать с помощью функции `length()`. С обоих сторон индексы берутся включительно.
С помощью индексирования можно не только вытаскивать имеющиеся значения в векторе, но и присваивать им новые:
```{r}
n[3] <- 20
n
```
Конечно, можно использовать целые векторы для индексирования:
```{r}
n[4:7]
n[10:1]
n[4:6] <- 0
n
```
Индексирование с минусом выдаст вам все значения вектора кроме выбранных:
```{r}
n[-1]
n[c(-4, -5)]
```
Минус здесь "выключает" выбранные значения из вектора, а не означает отсчет с конца как в Python.
Более того, можно использовать логический вектор для индексирования. В этом случае нужен логический вектор такой же длины:
```{r}
n[c(TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE)]
```
Логический вектор работает здесь как фильтр: пропускает только те значения, где на соответствующей позиции в логическом векторе для индексирования содержится `TRUE`, и не пропускает те значения, где на соответствующей позиции в логическом векторе для индексирования содержится `FALSE`.
![](images/013-vector_index_gandolf.png)
Ну а если эти два вектора (исходный вектор и логический вектор индексов) не равны по длине, то тут будет снова работать правило ресайклинга!
```{r}
n[c(TRUE, FALSE)] #то же самое - recycling rule!
```
Есть еще один способ индексирования векторов, но он несколько более редкий: индексирование по имени. Дело в том, что для значений векторов можно (но не обязательно) присваивать имена:
```{r}
my_named_vector <- c(first = 1,
second = 2,
third = 3)
my_named_vector['first']
```
А еще можно "вытаскивать" имена из вектора с помощью функции `names()` и присваивать таким образом новые имена.
```{r}
d <- 1:4
names(d) <- letters[1:4]
names(d)
d["a"]
```
> `letters` -- это "зашитая" в R константа -- вектор букв от a до z. Иногда это очень удобно! Кроме того, есть константа `LETTERS` -- то же самое, но заглавными буквами. А еще в R есть названия месяцев на английском и числовая константа `pi`.
Вернемся к нашему вектору `n` и посчитаем его среднее с помощью функции `mean()`:
```{r}
mean(n)
```
А как вытащить все значения, которые больше среднего?
Сначала получим логический вектор -- какие значения больше среднего:
```{r}
larger <- n > mean(n)
larger
```
А теперь используем его для индексирования вектора `n`:
```{r}
n[larger]
```
Можно все это сделать в одну строчку:
```{r}
n[n > mean(n)]
```
Предыдущая строчка отражает то, что мы будем постоянно делать в R: вычленять (subset) из данных отдельные куски на основании разных условий.
## Работа с логическими векторами {#sec-logic_vectors}
На работе с логическими векторами построено очень много удобных фишек, связанных со сравнением условий.
```{r}
eyes <- c("green", "blue", "blue", "brown", "green", "blue")
```
### `mean()` и `sum()` для подсчета пропорций и количества TRUE {#sec-logic_mean_sum}
Уже знакомая нам функция `sum()` позволяет посчитать количество `TRUE` в логическом векторе. Например, можно удобно посчитать сколько раз значение `"blue"` встречается в векторе `eyes`:
```{r}
eyes == "blue"
sum(eyes == "blue")
```
Функцию `mean()` можно использовать для подсчета пропорций `TRUE` в логическом векторе.
```{r}
eyes == "blue"
mean(eyes == "blue")
```
Умножив на 100, мы получим долю выраженную в процентах:
```{r}
mean(eyes == "blue") * 100
```
### `all()` и `any()` {#sec-all_any}
Функция `all()` выдает `TRUE` только когда все значения логического вектора на входе равны `TRUE`:
```{r}
all(eyes == "blue")
```
Функция `any()` выдает `TRUE` когда есть хотя бы одно значение `TRUE`:
```{r}
any(eyes == "blue")
```
Вместе с оператором `!` можно получить много дополнительных вариантов. Например, есть ли хотя бы один `FALSE` в векторе?
```{r}
any(!eyes == "blue")
!all(eyes == "blue")
```
Все ли значения в векторе равны `FALSE`?
```{r}
all(!eyes == "blue")
!any(eyes == "blue")
```
### Превращение логических значений в индексы: `which()` {#sec-which}
Как вы уже знаете, и логические векторы, и числовые вектора с индексами могут использоваться для индексирования векторов. Иногда может понадобиться превратить логический вектор в вектор индексов. Для этого есть функция `which()`
```{r}
which(eyes == "blue")
```
### оператор %in% и match() {#sec-in}
Часто возникает такая задача: нужно проверить вектор на равенство с хотя бы одним значением из другого вектора. Например, мы хотим вычленить всех зеленоглазых и голубоглазых. Может возникнуть идея сделать так:
```{r}
eyes[eyes == c("green", "blue")]
```
Перед нами самый страшный случай: результат *похож* на правильный, но не правильный! Попытайтесь самостоятельно понять почему этот ответ неверный и что произошло на самом деле.
А на самом деле мы просто сравнили два вектора, один из которых короче другого, следовательно, у нас сработало правило ресайклинга.
![](images/013-vector_logical_vectors_recycling.jpg)
Как мы видим, это совсем не то, что нам нужно! В данной ситуации нам подойдет сравнение с двумя значениями вместе с логическим ИЛИ.
```{r}
eyes[eyes == "green" | eyes == "blue"]
```
Однако это не очень удобно, особенно если значений больше 2. Тогда на помощь приходит оператор `%in%`, который выполняет именно то, что нам изначально нужно: выдает для каждого значения в векторе слева, есть ли это значение среди значений вектора справа.
```{r}
eyes[eyes %in% c("green", "blue")]
```
::: callout-warning
## *Для продвинутых:* `match()`
Основное преимущество оператора `%in%` в его простоте и понятности. У оператора `%in%` есть старший брат, более сложный и более мощный.
Функция `match()` работает похожим образом на `%in%`, но при совпадении значения в левом векторе с одним из значений в правом выдает индекс соответствующего значения вместо `TRUE`. Если же совпадений нет, то вместо `FALSE` функция `match()` выдает `NA` (что можно поменять параметром `nomatch =`).
```{r}
match(eyes, c("green", "blue"))
```
Зачем это может понадобиться? Во-первых, это способ соединить два набора данных (хотя для этого есть и более подходящие инструменты), во-вторых, так можно заменить все значения кроме выбранных заменить на `NA` (для чего тоже есть альтернативы).
```{r}
c("green", "blue")[match(eyes, c("green", "blue"))]
```
:::
## NA - пропущенные значения {#sec-na}
В реальных данных у нас часто чего-то не хватает. Например, из-за технической ошибки или невнимательности не получилось записать какое-то измерение. Для обозначения пропущенных значений в R есть специальное значение `NA` (расшифровывается как *Not Available* - недоступное значение). `NA` -- это не строка `"NA"`, не `0`, не пустая строка `""` и не `FALSE`. `NA` -- это `NA`. Большинство операций с векторами, содержащими `NA` будут выдавать `NA`:
```{r}
missed <- NA
missed == "NA"
missed == ""
missed == NA
```
Заметьте, даже сравнение `NA` c `NA` выдает `NA`. Это может прозвучать абсурдно: ну как же так, и то `NA`, и другое `NA` -- это же одно и то же, они должны быть равны! Не совсем: `NA` -- это отсутствие информации об объекте, неопределенность, неизвестная нам величина. Если мы не знаем двух значений (т.е. имеем два `NA`), то это еще не значит, что они равны.
Иногда наличие `NA` в данных очень бесит:
```{r}
n[5] <- NA
n
mean(n)
```
Получается, что наличие `NA` "заражает" неопределенностью все последующие действия. Что же делать?
Наверное, надо сравнить вектор с `NA` и исключить этих пакостников. Давайте попробуем:
```{r}
n == NA
```
Ах да, мы ведь только что узнали, что даже сравнение `NA` c `NA` приводит к `NA`! Сначала это может показаться нелогичным: ведь с обоих сторон `NA`, почему же тогда результат их сравнения -- это тоже `NA`, а не `TRUE`?
Дело в том, что сравнивая две неопределенности, вы не можете установить между ними знак равенства. Представим себе двух супергероев: Бэтмена и Спайдермена. Допустим, мы не знаем их рост:
```{r}
Batman <- NA
Spiderman <- NA
```
Одинаковый ли у них рост?
```{r}
Batman == Spiderman
```
Мы не знаем! Возможно, да, возможно, и нет. Поэтому у нас здесь остается неопределенность.
Так как же избавиться от `NA` в данных? Самый простой способ -- это функция `is.na()`:
```{r}
is.na(n)
```
Результат выполнения `is.na(n)` выдает `FALSE` на тех позициях, где у нас числа (или другие значения), и `TRUE` там, где у нас `NA`. Чтобы вычленить из вектора `n` все значения кроме `NA` нам нужно, чтобы было наоборот: `TRUE`, если это не `NA`, `FALSE`, если это `NA`. Здесь нам понадобится логический оператор НЕ `!` (мы его уже встречали -- см. @sec-type_logical), который инвертирует логические значения:
```{r}
n[!is.na(n)]
```
Ура, мы можем считать среднее без `NA`!
```{r}
mean(n[!is.na(n)])
```
Теперь Вы понимаете, зачем нужно отрицание (`!`)
::: callout-tip
## *Полезное:* `na.rm = TRUE`
Вообще, есть еще один способ посчитать среднее, если есть NA. Для этого надо залезть в хэлп по функции *mean()*:
```{r, eval = FALSE}
?mean()
```
В хэлпе мы найдем параметр `na.rm =`, который по умолчанию `FALSE`. Вы знаете, что нужно делать!
```{r}
mean(n, na.rm = TRUE)
```
`NA` может появляться в векторах разных типов. На самом деле, `NA` - это специальное значение в логических векторах, тогда как в векторах других типов `NA` появляется как `NA_integer_`, `NA_real_`, `NA_complex_` или `NA_character_`, но R обычно сам все переводит в нужный формат и показывает как просто `NA`. Таким образом, `NA` в векторах разных типов -- это разные `NA`, хотя на практике эта деталь обычно несущественна.
:::
::: callout-warning
## *Для продвинутых:* `NA` против `NaN`
Кроме `NA` есть еще `NaN` -- это разные вещи. `NaN` расшифровывается как *Not a Number* и получается в результате таких операций как `0 / 0`. Тем не менее, функция `is.na()` выдает `TRUE` на `NaN`, а вот функция `is.nan()` выдает `TRUE` на `NaN` и `FALSE` на `NA`:
```{r}
is.na(NA)
is.na(NaN)
is.nan(NA)
is.nan(NaN)
```
:::
## Заключение {#sec-vector_end}
Итак, с векторами мы более-менее разобрались. Помните, что вектора -- это один из краеугольных камней вашей работы в R. Если вы хорошо с ними разобрались, то дальше все будет довольно несложно. Тем не менее, вектора -- это не все. Есть еще два важных типа данных: **списки** ***(list)*** и **матрицы** ***(matrix)***. Их можно рассматривать как своеобразное "расширение" векторов, каждый в свою сторону. Ну а списки и матрицы нужны чтобы понять основной тип данных в R -- **датафрейм *(dataframe)***.
![](images/013-vector_matrix_list_df.jpg){width="400"}