-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy path01-intro_to_r.Rmd
1089 lines (718 loc) · 72.9 KB
/
01-intro_to_r.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Введение в R {#intro}
## Наука о данных
Наука о данных --- это новая область знаний, которая активно развивается в последнее время. Она находиться на пересечении компьютерных наук, статистики и математики, и трудно сказать, действительно ли это наука. При этом это движение развивается в самых разных научных направлениях, иногда даже оформляясь в отдельную отрасль:
* биоинформатика
* вычислительная криминалистика
* цифровые гуманитарные исследования
* датажурналистика
* ...
Все больше книг "Data Science for ...":
* psychologists [@hansjoerg19]
* immunologists [@thomas19]
* business [@provost13]
* public policy [@brooks13]
* fraud detection [@baesens15]
* ...
Среди умений датасаентистов можно перечислить следующие:
* сбор и обработка данных
* трансформация данных
* визуализация данных
* статистическое моделирование данных
* представление полученных результатов
* организация всей работы **воспроизводимым способом**
Большинство этих тем в той или иной мере будет представлено в нашем курсе.
## Установка R и RStudio
В данной книге используется исключительно R [@r_core_team19], так что для занятий понадобятся:
* R
* [на Windows](https://cran.r-project.org/bin/windows/base/)
* [на Mac](https://cran.r-project.org/bin/macosx/)
* [на Linux](https://cran.rstudio.com/bin/linux/), также можно добавить зеркало и установить из командной строки:
```
sudo apt-get install r-cran-base
```
* RStudio --- IDE для R ([можно скачать здесь](https://www.rstudio.com/products/rstudio/download/))
* и некоторые пакеты на R
Часто можно увидеть или услышать, что R --- язык программирования для "статистической обработки данных". Изначально это, конечно, было правдой, но уже давно R --- это полноценный язык программирования, который при помощи своих пакетов позволяет решать огромный спектр задач. В данной книге используется следующая версия R:
```{r, echo = FALSE}
sessionInfo()$R.version$version.string
```
Некоторые люди не любят устанавливать лишние программы себе на компьютер, несколько вариантов есть и для них:
* [RStudio cloud](https://rstudio.cloud/) --- полная функциональность RStudio, пока бесплатная, но скоро это исправят;
* [RStudio on rollApp](https://www.rollapp.com/app/rstudio) --- облачная среда, позволяющая разворачивать программы.
Первый и вполне закономерный вопрос: зачем мы ставили R и отдельно еще какой-то RStudio?
Если опустить незначительные детали, то R --- это сам язык программирования, а RStudio --- это среда (IDE), которая позволяет в этом языке очень удобно работать.
## Полезные ссылки
В интернете легко найти документацию и туториалы по самым разным вопросам в R, так что главный залог успеха --- грамотно пользоваться поисковиком, и лучше на английском языке.
* [книга [@wickham16]](https://r4ds.had.co.nz/) является достаточно сильной альтернативой всему курсу
* [stackoverflow](https://stackoverflow.com) --- сервис, где достаточно быстро отвечают на любые вопросы (не обязательно по R)
* [RStudio community](https://community.rstudio.com/) --- быстро отвечают на вопросы, связанные с R
* [русский stackoverflow](https://ru.stackoverflow.com)
* [R-bloggers](https://www.r-bloggers.com/) --- сайт, где собираются новинки, связанные с R
* [чат](https://t.me/rlang_ru), где можно спрашивать про R на русском (но почитайте [правила чата](https://github.com/r-lang-group-ru/group-rules/blob/master/README.md), перед тем как спрашивать)
* [чат](https://t.me/joinchat/CxZg5goGc6rlWGjcvOYrpA) по визуализации данных, [чат](https://t.me/ddjrus) датажурналистов
* [канал про визуализацию ](https://t.me/chartomojka), [дата-блог "Новой газеты"](https://t.me/novaya_data), ...
## Rstudio
Когда вы откроете RStudio первый раз, вы увидите три панели: консоль, окружение и историю, а также панель для всего остального. Если ткнуть в консоли на значок уменьшения, то можно открыть дополнительную панель, где можно писать скрипт.
```{r, echo=FALSE}
knitr::include_graphics("images/01_01_rstudio.png")
```
Существуют разные типы пользователей: одни любят работать в консоли (на картинке это **2 --- R Console**), другие предпочитают скрипты (**1 --- Code Editor**). Консоль позволяет использовать интерактивный режим команда-ответ, а скрипт является по сути текстовым документом, фрагменты которого можно для отладки запускать в консоли.
**3 --- Workspace and History**: Здесь можно увидеть переменные. Это поле будет автоматически обновляться по мере того, как Вы будете запускать строчки кода и создавать новые переменные. Еще там есть вкладка с историей последних команд, которые были запущены.
**4 --- Plots and files**: Здесь есть очень много всего. Во-первых, небольшой файловый менеджер, во-вторых, там будут появляться графики, когда вы будете их рисовать. Там же есть вкладка с вашими пакетами (Packages) и Help по функциям. Но об этом потом.
## Введение в R
### R как калькулятор {#calc}
Ой-ей, консоль, скрипт че-то все непонятно.
Давайте начнем с самого простого и попробуем использовать R как простой калькулятор. `+`, `-`, `*`, `/`, `^` (степень), `()` и т.д.
Просто запускайте в консоли пока не надоест:
```{r}
40+2
3-2
5*6
99/9
2^3
(2+2)*2
```
Ничего сложного, верно? Вводим выражение и получаем результат. Порядок выполнения арифметических операций как в математике, так что не забывайте про скобочки.
> Если Вы не уверены в том, какие операции имеют приоритет, то используйте скобочки, чтобы точно обозначить, в каком порядке нужно производить операции.
![](images/ThePracticalDev_2016-Apr-13.jpg)
```{r, echo = FALSE}
# Важно отметить, что R может служить лишь как калкулятор. Можно узнать, что $\cos(\frac{\pi}{2})$ приблизительно равно 0.9659258, но вы никогда не узнаете, что $\cos(\frac{\pi}{2})$ также равно $\sqrt{\frac{1+\frac{\sqrt{3}}{2}}{2}}$. Но при этом какие-то рутинные операции, с которыми вы, возможно, сталкивались (дифернцирование, интегрирование, нахождение корней полинома и др.), R делает с легкостью.
```
### Функции {#func}
Давайте теперь извлечем корень из какого-нибудь числа. В принципе, тем, кто помнит школьный курс математики, возведения в степень вполне достаточно:
```{r}
16^0.5
```
Ну а если нет, то можете воспользоваться специальной **функцией**: это обычно какие-то буквенные символы с круглыми скобками сразу после названия функции. Мы подаем на вход (внутрь скобочек) какие-то данные, внутри этих функций происходят какие-то вычисления, которые выдают в ответ какие-то другие данные (или же функция записывает файл, рисует график и т.д.).
Вот, например, функция для корня:
```{r}
sqrt(16)
```
> R --- case-sensitive язык, т.е. регистр важен. `SQRT(16)` не будет работать.
А вот так выглядит функция логарифма:
```{r}
log(8)
```
Так, вроде бы все нормально, но... Если Вы еще что-то помните из школьной математики, то должны понимать, что что-то здесь не так.
Здесь не хватает основания логарифма!
> Логарифм --- показатель степени, в которую надо возвести число, называемое основанием, чтобы получить данное число.
То есть у логарифма 8 по основанию 2 будет значение 3:
$\log_2 8 = 3$
То есть если возвести 2 в степень 3 у нас будет 8:
$2^3 = 8$
Только наша функция считает все как-то не так.
Чтобы понять, что происходит, нам нужно залезть в хэлп этой функции:
```{r, eval = F}
?log
```
Справа внизу в RStudio появится вот такое окно:
![](images/help.png)
Действительно, у этой функции есть еще аргумент *`base =`*. По дефолту он равен числу Эйлера (`r exp(1)`...), т.е. функция считает натуральный логарифм.
В большинстве функций R есть какой-то основной инпут --- данные в том или ином формате, а есть и дополнительные параметры, которые можно прописывать вручную, если параметры по умолчанию нас не устраивают.
```{r}
log(x = 8, base = 2)
```
...или просто (если Вы уверены в порядке аргументов):
```{r}
log(8,2)
```
Более того, Вы можете использовать оутпут одних функций как инпут для других:
```{r}
log(8, sqrt(4))
```
Если эксплицитно писать имена аргументов, то их порядок в функции не важен:
```{r}
log(base = 2, x = 8)
```
А еще можно недописывать имена аргументов, если они не совпадают с другими:
```{r}
log(b = 2, x = 8)
```
Мы еще много раз будем возвращаться к функциям. Вообще, функции --- это одна из важнейших штук в R (примерно так же как и в Python). Мы будем создавать свои функции, использовать функции как инпут для функций и многое-многое другое. В R очень крутые возможности работы с функциями. Поэтому подружитесь с функциями, они клевые.
> Арифметические знаки, которые мы использовали: +,-,/,^ и т.д. называются **операторами** и на самом деле тоже являются функциями:
```{r}
'+'(3,4)
```
### Переменные {#variables}
Важная штука в программировании на практически любом языке --- возможность сохранять значения в **переменных**. В R это обычно делается с помощью вот этих символов: *<-* (но можно использовать и обычное *=*, хотя это не очень принято). Для этого есть удобное сочетание клавиш: нажмите одновременно `Alt -` (или `option -` на Маке).
```{r}
a <- 2
a
```
После присвоения переменная появляется во вкладке **Environment** в RStudio:
![](images/env.png)
Можно использовать переменные в функциях и просто вычислениях:
```{r}
b <- a^a+a*a
b
log(b,a)
```
Вы можете сравнивать разные переменные:
```{r}
a == b
```
Заметьте, что сравнивая две переменные мы используем два знака равно ==, а не один =. Иначе это будет означать присвоение.
```{r}
a = b
a
```
Теперь Вы сможете понять комикс про восстание роботов на следующей странице (пусть он и совсем про другой язык программирования)
![](images/WaCM5x3mvQM.jpg)
Этот комикс объясняет, как важно не путать присваивание и сравнение *(хотя я иногда путаю до сих пор =( )*.
Иногда нам нужно проверить на *не*равенство:
```{r}
a <- 2
b <- 3
a==b
a!=b
```
Восклицательный язык в программировании вообще и в R в частности стандартно означает отрицание.
Еще мы можем сравнивать на больше/меньше:
```{r}
a>b
a<b
a>=b
a<=b
```
## Типы данных {#data_types}
До этого момента мы работали только с числами (numeric):
```{r}
class(a)
```
> Вообще, в R много типов numeric: integer (целые), double (с десятичной дробью), complex (комплексные числа). Последние пишутся так: `complexnumber <- 2+2i`
> Однако в R с этим обычно можно вообще не заморачиваться, R сам будет конвертить между форматами при необходимости. Немного подробностей здесь:
[Разница между numeric и integer](https://stackoverflow.com/questions/23660094/whats-the-difference-between-integer-class-and-numeric-class-in-r), [Как работать с комплексными числами в R](http://www.r-tutor.com/r-introduction/basic-data-types/complex)
Теперь же нам нужно ознакомиться с двумя другими важными типами данных в R:
1. **character**: строки символов. Они должны выделяться кавычками. Можно использовать как `"`, так и `'` (что удобно, когда строчка внутри уже содержит какие-то кавычки).
```{r}
s <- "Всем привет!"
s
class(s)
```
2. **logical**: просто `TRUE` или `FALSE`.
```{r}
t1 <- TRUE
f1 <- FALSE
t1
f1
```
Вообще, можно еще писать `T` и `F` (но не `True` и `False`!)
```{r}
t2 <- T
f2 <- F
```
Это дурная практика, так как R защищает от перезаписи переменные `TRUE` и `FALSE`, но не защищает от этого `T` и `F`
```{r error=TRUE}
TRUE <- FALSE
TRUE
T <- FALSE
T
```
Теперь вы можете догадаться, что результаты сравнения, например, числовых или строковых переменных вы можете сохранять в переменные тоже!
```{r}
comparison <- a == b
comparison
```
Это нам очень понадобится, когда мы будем работать с реальными данными: нам нужно будет постоянно вытаскивать какие-то данные из датасета, а это как раз и построено на игре со сравнением переменных.
Чтобы этим хорошо уметь пользоваться, нам нужно еще освоить как работать с логическими операторами. Про один мы немного уже говорили --- это не (`!`):
```{r}
t1
!t1
!!t1 #Двойное отрицание!
```
Еще есть И (выдаст `TRUE` только в том случае если обе переменные `TRUE`):
```{r}
t1&t2
t1&f1
```
А еще ИЛИ (выдаст `TRUE` в случае если хотя бы одна из переменных `TRUE`):
```{r}
t1 | f1
f1 | f2
```
Если кому-то вдруг понадобиться другое ИЛИ --- есть функция `xor()`, принимающий два аргумента.
Поздравляю, мы только что разобрались с самой занудной частью.
Пора переходить к важному и интересному. ВЕКТОРАМ!
## Вектор {#atomic}
Если у вас не было линейной алгебры (или у вас с ней было все плохо), то просто запомните, что **вектор** (или **atomic vector** или **atomic**) --- это набор (столбик) чисел в определенном порядке.
> P.S. Если вы привыкли из школьного курса физики считать вектора стрелочками, то не спешите возмущаться и паниковать. Представьте стрелочки как точки из нуля координат {0,0} до какой-то точки на координатной плоскости, например, {2,1}. Вот последние два числа и будем считать вектором. Поэтому постарайтесь на время выбросить стрелочки из головы.
На самом деле, мы уже работали с векторами в R, но, возможно, Вы об этом даже не догадывались. Дело в том, что в R нет как таковых "значений", **есть вектора длиной 1**. Такие дела!
Чтобы создать вектор из нескольких значений, нужно воспользоваться функцией *`c()`*:
```{r}
c(4,8,15,16,23,42)
c("Хэй", "Хэй", "Ха")
```
>Одна из самых мерзких и раздражающих причин ошибок в коде --- это использование `с` из кириллицы вместо `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, 1:3)
```
Еще можно объединять вектора (что мы, по сути, и делали, просто с векторами длиной 1):
```{r}
v1 <- c("Hey", "Ho")
v2 <- c("Let's", "Go!")
c(v1,v2)
```
### Coercion {#coercion}
Что будет, если вы объедините два вектора с значениями разных типов? Ошибка?
Мы уже обсуждали, что в *atomic* может быть только один тип данных. В некоторых языках программирования при операции с данными разных типов мы бы получили ошибку. А вот в R при несовпадении типов пройзойдет попытка привести типы к "общему знаменателю", то есть конвертировать данные в более "широкий" тип.
Например:
```{r}
c(FALSE, 2)
```
`FALSE` превратился в `0` (а `TRUE` превратился бы в `1`), чтобы можно было оба значения объединить в вектор. То же самое произошло бы в случае операций с векторами:
```{r}
2 + TRUE
```
Это называется **coercion**.
Более сложный пример:
```{r}
c(TRUE, 3, "Привет")
```
У R есть иерархия коэрсинга:
`NULL < raw < logical < integer < double < complex < character < list < expression`.
Мы из этого списка еще многого не знаем, сейчас важно запомнить, что логические данные --- `TRUE` и `FALSE` --- превращаются в `0` и `1` соответственно, а `0` и `1` в строчки `"0"` и `"1"`.
Если Вы боитесь полагаться на coercion, то можете воспользоваться функциями `as.нужныйтипданных`:
```{r}
as.numeric(c(TRUE, FALSE, FALSE))
as.character(as.numeric(c(TRUE, FALSE, FALSE)))
```
Можно превращать и обратно, например, строковые значения в числовые. Если среди числа встретится буква или другой неподходящий знак, то мы получим предупреждение `NA` --- пропущенное значение (мы очень скоро научимся с ними работать).
```{r}
as.numeric(c("1", "2", "три"))
```
### Операции с векторами {#vector_op}
Все те арифметические операции, что мы использовали ранее, можно использовать с векторами одинаковой длины:
```{r}
n <- 1:4
m <- 4:1
n + m
n - m
n * m
n / m
n ^ m + m * (n - m)
```
> Если после какого-нибудь MATLAB Вы привыкли, что по умолчанию операторы работают по правилам линейной алгебры и `m*n` будет давать скалярное произведение (dot product), то снова нет. Для скалярного произведения нужно использовать операторы с `%` по краям:
```{r}
n %*% m
```
> Абсолютно так же и с операциями с матрицами в R, хотя про матрицы будет немного позже.
В принципе, большинство функций в R, которые работают с отдельными значениями, так же хорошо работают и с целыми векторами. Скажем, Вы хотите извлечь корень из нескольких чисел, для этого не нужны никакие циклы (как это обычно делается в других языках программирования). Можно просто "скормить" вектор функции и получить результат применения функции к каждому элементу вектора:
```{r}
sqrt(1:10)
```
### Recycling {#recycling}
Допустим мы хотим совершить какую-нибудь операцию с двумя векторами. Как мы убедились, с этим обычно нет никаких проблем, если они совпадают по длине. А что если вектора не совпадают по длине?
Ничего страшного! Здесь будет работать правило ресайклинга (**recycling** = *правило переписывания*). Это означает, что если короткий вектор кратен по длине длинному, то он будет повторять короткий необходимое количество раз:
```{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). А вот ресайклинг кратных по длине векторов --- это очень удобная штука, которая используется очень часто.
### Индексирование векторов {#index_atomic}
Итак, мы подошли к одному из самых сложных моментов. И одному из основных. От того, как хорошо вы научись с этим работать, зависит весь Ваш дальнейший успех на R-поприще!
Речь пойдет об **индексировании** векторов. Задача, которую Вам придется решать каждые пять минут работы в R - как выбрать из вектора (или же списка, матрицы и датафрейма) какую-то его часть. Для этого используются квадратные скобочки `[]` (не круглые - они для функций!).
Самое простое - индексировать по номеру индекса, т.е. порядку значения в векторе.
```{r}
n <- 1:10
n[1]
n[10]
```
> Если вы знакомы с другими языками программирования (не MATLAB, там все так же) и уже научились думать, что индексация с 0 --- это очень удобно и очень правильно (ну или просто свыклись с этим), то в R Вам придется переучиться обратно. Здесь первый индекс --- это 1, а последний равен длине вектора --- ее можно узнать с помощью функции `length()`. С обоих сторон индексы берутся включительно.
С помощью индексирования можно не только вытаскивать имеющиеся значения в векторе, но и присваивать им новые:
```{r}
n[3] <- 20
n
```
Конечно, можно использовать целые векторы для индексирования:
```{r}
n[4:7]
n[10:1]
```
Индексирование с минусом выдаст вам все значения вектора кроме выбранных (простите, пользователя Python, которые ожидают здесь отсчет с конца...):
```{r}
n[-1]
n[c(-4, -5)]
```
Более того, можно использовать логический вектор для индексирования. В этом случае нужен логический вектор такой же длины:
```{r}
n[c(TRUE,FALSE,TRUE,FALSE,TRUE,FALSE,TRUE,FALSE,TRUE,FALSE)]
```
Ну а если они не равны, то тут будет снова работать правило ресайклинга!
```{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]
d["a"]
```
> `letters` - это "зашитая" в R константа - вектор букв от a до z. Иногда это очень удобно! Кроме того, есть константа `LETTERS` - то же самое, но заглавными буквами. А еще есть названия месяцев на английском и числовая константа `pi`.
Теперь посчитаем среднее вектора `n`:
```{r}
mean(n)
```
А как вытащить все значения, которые больше среднего?
Сначала получим логический вектор --- какие значения больше среднего:
```{r}
larger <- n > mean(n)
larger
```
А теперь используем его для индексирования вектора `n`:
```{r}
n[larger]
```
Можно все это сделать в одну строчку:
```{r}
n[n>mean(n)]
```
Предыдущая строчка отражает то, что мы будем постоянно делать в R: вычленять (subset) из данных отдельные куски на основании разных условий.
### NA --- пропущенные значения {#na}
В реальных данных у нас часто чего-то не хватает. Например, из-за технической ошибки или невнимательности не получилось записать какое-то измерение. Для этого в R есть `NA`. `NA` --- это не строка `"NA"`, не `0`, не пустая строка `""` и не `FALSE`. `NA` --- это `NA`.
Большинство операций с векторами, содержащими `NA` будут выдавать `NA`:
```{r}
missed <- NA
missed == "NA"
missed == ""
missed == NA
```
Заметьте: даже сравнение `NA` c `NA` выдает `NA`!
Иногда `NA` в данных очень бесит:
```{r}
n[5] <- NA
n
mean(n)
```
Что же делать?
Наверное, надо сравнить вектор с `NA` и исключить этих пакостников. Давайте попробуем:
```{r}
n == NA
```
Ах да, мы ведь только что узнали, что даже сравнение `NA` c `NA` приводит к `NA`.
Чтобы выбраться из этой непростой ситуации, используйте функцию `is.na()`:
```{r}
is.na(n)
```
Результат выполнения `is.na(n)` выдает `FALSE` в тех местах, где у нас числа и `TRUE` там, где у нас `NA`. Нам нужно сделать наоборот. Здесь нам понадобится оператор `!` (мы его уже встречали), который инвертирует логические значения:
```{r}
n[!is.na(n)]
```
Ура, мы можем считать среднее!
```{r}
mean(n[!is.na(n)])
```
Теперь Вы понимаете, зачем нужно отрицание (`!`)
Вообще, есть еще один из способов посчитать среднее, если есть `NA`. Для этого надо залезть в хэлп по функции *mean()*:
```{r, eval = FALSE}
?mean()
```
В хэлпе мы найдем параметр `na.rm =`, который по дефолту `FALSE`. Вы знаете, что нужно делать!
```{r}
mean(n, na.rm = TRUE)
```
Еееее!
> `NA` может появляться в векторах других типов тоже. Кроме `NA` есть еще `NaN` --- это разные вещи. `NaN` расшифровывается как Not a Number и получается в результате таких операций как `0/0`.
### В любой непонятной ситуации --- ищите в поисковике {#google}
Если вдруг вы не знаете, что искать в хэлпе, или хэлпа попросту недостаточно, то... ищите в поисковике!
![](images/2AmXWgVoULk.jpg)
Нет ничего постыдного в том, чтобы искать в Интернете решения проблем. Это абсолютно нормально. Используйте силу интернета во благо и да помогут Вам *Stackoverflow* и бесчисленные R-туториалы!
<blockquote class="twitter-tweet" data-lang="en">
<p lang="en" dir="ltr">Computer Programming To Be Officially Renamed “Googling Stack Overflow”<br><br>Source: <a href="http://t.co/xu7acfXvFF">http://t.co/xu7acfXvFF</a> <a href="http://t.co/iJ9k7aAVhd">pic.twitter.com/iJ9k7aAVhd</a></p>— Stack Exchange <a href="https://twitter.com/StackExchange/status/623139544276299776?ref_src=twsrc%5Etfw">July 20, 2015</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
Главное, помните: загуглить работающий ответ всегда недостаточно. Надо понять, как и почему он работает. Иначе что-то обязательно пойдет не так.
Кроме того, правильно загуглить проблему --- не так уж и просто.
<blockquote class="twitter-tweet" data-lang="en">
<p lang="en" dir="ltr">Does anyone ever get good at R or do they just get good at googling how to do things in R</p>— 🔬🖤Lauren M. Seyler, Ph.D.❤️⚒ href="https://twitter.com/mousquemere/status/1125522375141883907?ref_src=twsrc%5Etfw">May 6, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
Итак, с векторами мы более-менее разобрались. Помните, что вектора --- это один из краеугольных камней Вашей работы в R. Если Вы хорошо с ними разобрались, то дальше все будет довольно несложно. Тем не менее, вектора --- это не все. Есть еще два важных типа данных: списки (**list**) и матрицы (**matrix**). Их можно рассматривать как своеобразное "расширение" векторов, каждый в свою сторону. Ну а списки и матрицы нужны чтобы понять основной тип данных в R --- **data.frame**.
![](images/New-Mind-Map.jpg)
## Матрицы (matrix) {#matrix}
Если вдруг Вас пугает это слово, то совершенно зря. Матрица --- это всего лишь "двумерный" вектор: вектор, у которого есть не только длина, но и ширина. Создать матрицу можно с помощью функции `matrix()` из вектора, указав при этом количество строк и столбцов.
```{r}
A <- matrix(1:20, nrow=5,ncol=4)
A
```
Если мы знаем сколько значений в матрице и сколько мы хотим строк, то количество столбцов указывать необязательно:
```{r}
A <- matrix(1:20, nrow=5)
A
```
Все остальное так же как и с векторами: внутри находится данные только одного типа. Поскольку матрица --- это уже двумерный массив, то у него имеется два индекса. Эти два индекса разделяются запятыми.
```{r}
A[2,3]
A[2:4, 1:3]
```
Первый индекс --- выбор строк, второй индекс --- выбор колонок. Если же мы оставляем пустое поле вместо числа, то мы выбираем все строки/колонки в зависимости от того, оставили мы поле пустым до или после запятой:
```{r}
A[,1:3]
A[2:4,]
A[,]
```
В принципе, это все, что нам нужно знать о матрицах. Матрицы используются в R довольно редко, особенно по сравнению, например, с MATLAB. Но вот индексировать матрицы хорошо бы уметь: это понадобится в работе с датафреймами.
> То, что матрица - это просто двумерный вектор, не является метафорой: в R матрица - это по сути своей вектор с дополнительными *атрибутами* `dim` и `dimnames`. Атрибуты --- это неотъемлемые свойства объектов, для всех объектов есть обязательные атрибуты типа и длины и могут быть любые необязательные атрибуты. Можно задавать свои атрибуты или удалять уже присвоенные: удаление атрибута `dim` у матрицы превратит ее в обычный вектор. Про атрибуты подробнее можно почитать [здесь](https://perso.esiee.fr/~courivad/R/06-objects.html) или на стр. 99--101 книги "R in a Nutshell" [@adler2010r].
## Списки (list) {#list}
Теперь представим себе вектор без ограничения на одинаковые данные внутри. И получим список!
```{r}
l <- list(42, "Пам пам", TRUE)
l
```
А это значит, что там могут содержаться самые разные данные, в том числе и другие списки и векторы!
```{r}
lbig <- list(c("Wow", "this", "list", "is", "so", "big"), "16", l)
lbig
```
Если у нас сложный список, то есть очень классная функция, чтобы посмотреть, как он устроен, под названием `str()`:
```{r}
str(lbig)
```
Как и в случае с векторами мы можем давать имена элементам списка:
```{r}
namedl <- list(age = 24, PhDstudent = T, language = "Russian")
namedl
```
К списку можно обращаться как с помощью индексов, так и по именам. Начнем с последнего:
```{r}
namedl$age
```
А вот с индексами сложнее, и в этом очень легко запутаться. Давайте попробуем сделать так, как мы делали это раньше:
```{r}
namedl[1]
```
Мы, по сути, получили элемент списка - просто как часть списка, т.е. как список длиной один:
```{r}
class(namedl)
class(namedl[1])
```
А вот чтобы добраться до самого элемента списка (и сделать с ним что-то хорошее) нам нужна не одна, а две квадратных скобочки:
```{r}
namedl[[1]]
class(namedl[[1]])
```
<blockquote class="twitter-tweet" data-lang="en">
<p lang="en" dir="ltr">Indexing lists in <a href="https://twitter.com/hashtag/rstats?src=hash&ref_src=twsrc%5Etfw">#rstats</a>. Inspired by the Residence Inn <a href="http://t.co/YQ6axb2w7t">pic.twitter.com/YQ6axb2w7t</a></p>— Hadley Wickham (@ href="https://twitter.com/hadleywickham/status/643381054758363136?ref_src=twsrc%5Etfw">September 14, 2015</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8">
</script>
Как и в случае с вектором, к элементу списка можно обращаться по имени.
```{r list}
namedl[['age']]
```
Хотя последнее --- практически то же самое, что и использование знака $.
> Списки довольно часто используются в R, но реже, чем в Python. Со многими объектами в R, такими как результаты статистических тестов, объекты ggplot и т.д. удобно работать именно как со списками --- к ним все вышеописанное применимо. Кроме того, некоторые данные мы изначально получаем в виде древообразной структуры --- хочешь не хочешь, а придется работать с этим как со списком. Но обычно после этого стоит как можно скорее превратить список в датафрейм.
## Data.frame {#df}
Итак, мы перешли к самому главному. Самому-самому. Датафреймы (**data.frames**). Более того, сейчас станет понятно, зачем нам нужно было разбираться со всеми предыдущими темами.
Без векторов мы не смогли бы разобраться с матрицами и списками. А без последних мы не сможем понять, что такое датафрейм.
```{r}
name <- c("Ivan", "Eugeny", "Lena", "Misha", "Sasha")
age <- c(26, 34, 23, 27, 26)
student <- c(FALSE, FALSE, TRUE, TRUE, TRUE)
df = data.frame(name, age, student)
df
str(df)
```
Вообще, очень похоже на список, не правда ли? Так и есть, датафрейм --- это что-то вроде проименованного списка, каждый элемент которого является atomic вектором фиксированной длины. Скорее всего, список Вы представляли "горизонтально". Если это так, то теперь "переверните" его у себя в голове. Так, чтоб названия векторов оказались сверху, а колонки стали столбцами. Поскольку длина всех этих векторов равна (обязательное условие!), то данные представляют собой табличку, похожую на матрицу. Но в отличие от матрицы, разные столбцы могут имет разные типы данных: первая колонка --- character, вторая колонка --- numeric, третья колонка --- logical. Тем не менее, обращаться с датафреймом можно и как с проименованным списком, и как с матрицей:
```{r}
df$age[2:3]
```
Здесь мы сначала вытащили колонку `age` с помощью оператора `$`. Результатом этой операции является числовой вектор, из которого мы вытащили кусок, выбрав индексы `2` и `3`.
Используя оператор `$` и присваивание можно создавать новые колонки датафрейма:
```{r}
df$lovesR <- TRUE #правило recycling - узнали?
df
```
Ну а можно просто обращаться с помощью двух индексов через запятую, как мы это делали с матрицей:
```{r}
df[3:5, 2:3]
```
Как и с матрицами, первый индекс означает строчки, а второй --- столбцы.
А еще можно использовать названия колонок внутри квадратных скобок:
```{r}
df[1:2,"age"]
```
И здесь перед нами открываются невообразимые возможности! Узнаем, любят ли R те, кто моложе среднего возраста в группе:
```{r}
df[df$age < mean(df$age), 4]
```
Эту же задачу можно выполнить другими способами:
```{r dataframe}
df$lovesR[df$age < mean(df$age)]
df[df$age < mean(df$age), 'lovesR']
```
В большинстве случаев подходят сразу несколько способов --- тем не менее, стоит овладеть ими всеми.
Датафреймы удобно просматривать в RStudio. Для это нужно написать команду `View(df)` или же просто нажать на названии нужной переменной из списка вверху справа (там где Environment). Тогда увидите табличку, очень похожую на Excel и тому подобные программы для работы с таблицами. Там же есть и всякие возможности для фильтрации, сортировки и поиска... Но, конечно, интереснее все эти вещи делать руками, т.е. с помощью написания кода.
На этом пора заканчивать с введением и приступать к реальным данным.
## Начинаем работу с реальными данными {#real_data}
Итак, пришло время перейти к реальным данным. Мы начнем с использования датасета (так мы будем называть любой набор данных) по Игре Престолов, а точнее, по книгам цикла *"Песнь льда и пламени"* Дж. Мартина. Да, будут спойлеры, но сериал уже давно закончился и сильно разошелся с книгами...
### Рабочая папка и проекты {#wd}
Для начала скачайте файл по [ссылке](https://raw.githubusercontent.com/Pozdniakov/stats/master/data/character-deaths.csv)
Он, скорее всего, появился у Вас в папке "Загрузки". Если мы будем просто пытаться прочитать этот файл (например, с помощью `read.csv()` --- мы к этой функцией очень скоро перейдем), указав его имя и разрешение, то наткнемся на такую ошибку:
> Ошибка в file(file, "rt") :не могу открыть соединение
> Вдобавок: Предупреждение:
> В file(file, "rt") :
> не могу открыть файл 'character-deaths.csv': No such file or directory
Это означает, что R не может найти нужный файл. Вообще-то мы даже не сказали, где искать. Нам нужно как-то совместить место, где R ищет загружаемые файлы и сами файлы. Для этого есть несколько способов.
- Магомет идет к горе: перемещение файлов в рабочую папку.
Для этого нужно узнать, какая папка является рабочей с помощью функции `getwd()` (без аргументов), найти эту папку в проводнике и переместить туда файл. После этого можно использовать просто название файла с разрешением:
```{r, eval = FALSE}
got <- read.csv("character-deaths.csv")
```
- Гора идет к Магомету: изменение рабочей папки.
Можно просто сменить рабочую папку с помощью `setwd()` на ту, где сейчас лежит файл, прописав путь до этой папки. Теперь файл находится в рабочей папке:
```{r, eval = FALSE}
got <- read.csv("character-deaths.csv")
```
Этот вариант использовать не рекомендуется. Как минимум, это сразу делает невозможным запустить скрипт на другом компьютере.
- Гора находит Магомета по месту прописки: указание полного пути файла.
```{r, eval = FALSE}
got <- read.csv("/Users/Username/Some_Folder/character-deaths.csv")
```
Этот вариант страдает теми же проблемами, что и предыдущий, поэтому тоже не рекомендуется.
> Для пользователей Windows есть дополнительная сложность: знак `/` является особым знаком для R, поэтому вместо него нужно использовать двойной `//`.
- Магомет использует кнопочный интерфейс: Import Dataset.
Во вкладке Environment справа в окне RStudio есть кнопка "Import Dataset". Возможно, у Вас возникло непреодолимое желание отдохнуть от написания кода и понажимать кнопочки --- сопротивляйтесь этому всеми силами, но не вините себя, если не сдержитесь.
- Гора находит Магомета в интернете.
Многие функции в R, предназначенные для чтения файлов, могут прочитать файл не только на Вашем компьютере, но и сразу из интернета. Для этого просто используйте ссылку вместо пути:
```{r import}
got <- read.csv("https://raw.githubusercontent.com/Pozdniakov/stats/master/data/character-deaths.csv")
```
- Каждый Магомет получает по своей горе: использование проектов в RStudio.
На первый взгляд это кажется чем-то очень сложным, но это не так. Это очень просто и ОЧЕНЬ удобно. При создании проекта создается отдельная папочка, где у Вас лежат данные, хранятся скрипты, вспомогательные файлы и отчеты. Если нужно вернуться к другому проекту --- просто открываете другой проект, с другими файлами и скриптами. Это еще помогает не пересекаться переменным из разных проектов --- а то, знаете, использование двух переменных `data` в разных скриптах чревато ошибками. Поэтому очень удобным решением будет выделение отдельного проекта под этот курс.
### Импорт данных {#import}
Как Вы уже поняли, импортирование данных - одна из самых муторных и неприятных вещей в R. Если у Вас получится с этим справится, то все остальное - ерунда. Мы уже разобрались с первой частью этого процесса - нахождением файла с данными, осталось научиться их читать.
Здесь стоит сделать небольшую ремарку. Довольно часто данные представляют собой табличку. Или же их можно свести к табличке. Такая табличка, как мы уже выяснили, удобно репрезентируется в виде датафрейма. Но как эти данные хранятся на компьютере? Есть два варианта: в *бинарном* и в *текстовом* файле.
Текстовый файл означает, что такой файл можно открыть в программе "Блокнот" или ее аналоге и увидеть напечатанный текст: скрипт, роман или упорядоченный набор цифр и букв. Нас сейчас интересует именно последний случай. Таблица может быть представлена как текст: отдельные строчки в файле будут разделять разные строчки таблицы, а какой-нибудь знак-разделитель отделет колонки друг от друга.
Для чтения данных из текстового файла есть довольно удобная функция `read.table()`. Почитайте хэлп по ней и ужаснитесь: столько разных параметров на входе! Но там же вы увидете функции `read.csv()`, `read.csv2()` и некоторые другие --- по сути, это тот же `read.table()`, но с другими дефолтными параметрами, соответствующие формату файла, который мы загружаем. В данном случае используется формат .csv, что означает Comma Separated Values (Значения, Разделенные Запятыми). Это просто текстовый файл, в котором "закодирована" таблица: разные строчки разделяют разные строчки таблицы, а столбцы отделяются запятыми. С этим связана одна проблема: в некоторых странах (в т.ч. и России) принято использовать запятую для разделения дробной части числа, а не точку, как это делается в большинстве стран мира. Поэтому есть "другой" формат .csv, где значения разделены точкой с запятой (`;`), а дробные значения - запятой (`,`). В этом и различие функций `read.csv()` и `read.csv2()` --- первая функция предназначена для "международного" формата, вторая - для (условно) "Российского".
В первой строчке обычно содержатся названия столбцов - и это чертовски удобно, функции `read.csv()` и `read.csv2()` по дефолту считают первую строчку именно как название для колонок.
Итак, прочитаем наш файл. Для этого используем только параметр `file = `, который идет первым, и для параметра `stringsAsFactors = ` поставим значение `FALSE`:
```{r, eval = FALSE}
got <- read.csv("data/character-deaths.csv", stringsAsFactors = FALSE)
```
> По сути, факторы - это примерно то же самое, что и character, но закодированные числами. Когда-то это было придумано для экономии используемых времени и памяти, сейчас же обычно становится просто лишней морокой. Но некоторые функции требуют именно character, некоторые factor, в большинстве случаев это без разницы. Но иногда непонимание может привести к дурацким ошибкам. В данном случае мы просто пока обойдемся без факторов.
Можете проверить с помощью `View(got)`: все работает! Если же вылезает какая-то странная ерунда или же просто ошибка - попробуйте другие функции и покопаться с параметрами. Для этого читайте Help.
Кроме .csv формата есть и другие варианты хранения таблиц в виде текста. Например, .tsv
- тоже самое, что и .csv, но разделитель - знак табуляции. Для чтения таких файлов есть функция `read.delim()` и `read.delim2()`. Впрочем, даже если бы ее и не было, можно было бы просто подобрать нужные параметры для функции `read.table()`. Есть даже функции, которые пытаются сами "угадать" нужные параметры для чтения --- часто они справляются с этим довольно удачно. Но не всегда. Поэтому стоит научиться справляться с любого рода данными на входе.
Тем не менее, далеко не всегда таблицы представлены в виде текстового файла. Самый распространенный пример таблицы в бинарном виде --- родные форматы Microsoft Excel. Если Вы попробуете открыть .xlsx файл в Блокноте, то увидите кракозябры. Это делает работу с этим файлами гораздо менее удобной, поэтому стоит избегать экселевских форматов и стараться все сохранять в .csv.
> Для работы с экселевскими файлами есть много пакетов: readxl, xlsx, openxlsx. Для чтения файлов SPSS, Stata, SAS есть пакет foreign. Что такое пакеты и как их устанавливать мы изучим позже.
## Препроцессинг данных в R {#prep}
Вчера мы узнали про основы языка R, про то, как работать с векторами, списками, матрицами и, наконец, датафреймами. Мы закончили день на загрузке данных, с чего мы и начнем сегодня:
```{r day2}
got <- read.csv("data/character-deaths.csv", stringsAsFactors = F)
```
После загрузки данных стоит немного "осмотреть" получившийся датафрейм `got`.
### Исследование данных {#explore}
Ок, давайте немного поизучаем датасет. Обычно мы привыкли глазами пробегать по данным, листая строки и столбцы --- и это вполне правильно и логично, от этого не нужно отучаться. Но мы можем дополнить наш базовый зрительнопоисковой инструментарий несколькими полезными командами.
Во-первых, вспомним другую полезную функцию `str()`:
```{r}
str(got)
```
Давайте разберемся с переменными в датафрейме:
Колонка `Name` --- здесь все понятно. Важно, что эти имена записаны абсолютно по-разному: где-то с фамилией, где-то без, где-то в скобочках есть пояснения. Колонка `Allegiances` --- к какому дому принадлежит персонаж. С этим сложно, иногда они меняют дома, здесь путаются сами семьи и персонажи, лояльные им. Особой разницы между `Stark` и `House Stark` нет. Следующие колонки - `Death Year`, `Book.of.Death`, `Death.Chapter`, `Book.Intro.Chapter` --- означают номер главы, в которой персонаж впервые появляется, а так же номер книги, глава и год (от завоевания Вестероса Эйгоном Таргариеном), в которой персонаж умирает. `Gender` --- `1` для мужчин, `0` для женщин. `Nobility` --- дворянское происхождение персонажа. Последние 5 столбцов содержат информацию, появлялся ли персонаж в книге (всего книг пока что 5).
Другая полезная функция для больших таблиц --- функция `head()`: она выведет первые несколько (по дефолту 6) строчек датафрейма.
```{r}
head(got)
```
Есть еще функция `tail()`. Догадайтесь сами, что она делает.
Для некоторых переменных полезно посмотреть таблицы частотности с помощью функции table():
```{r}
table(got$Allegiances)
```
Уау! Очень просто и удобно, не так ли? Функция `table()` может принимать сразу несколько столбцов. Это удобно для получения *таблиц сопряженности*:
```{r}
table(got$Allegiances, got$Gender)
```
### Subsetting {#subset}
Как мы обсуждали на прошлом занятии, мы можем сабсеттить (выделять часть датафрейма) датафрейм, обращаясь к нему и как к матрице: *датафрейм[вектор_с_номерами_строк, вектор_с_номерами_колонок]*
```{r}
got[100:115, 1:2]
```