forked from info201/book
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathvectors.Rmd
More file actions
354 lines (239 loc) · 17.5 KB
/
vectors.Rmd
File metadata and controls
354 lines (239 loc) · 17.5 KB
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
# Vectors
This chapter covers the foundational concepts for working with vectors in R. Vectors are _the_ fundamental data type in R: in order to use R, you need to become comfortable with vectors. This chapter will discuss how R stores information in vectors, the way in which operations are executed in _vectorized_ form, and how to extract subsets of vectors. These concepts are **key to effectively programming** in R.
## What is a Vector?
**Vectors** are _one-dimensional collections of values_ that are all stored in a single variable. For example, you can make a vector `people` that contains the character strings "Sarah", "Amit", and "Zhang". Alternatively, you could make a vector `one_to_ninety` that stores the numbers from 1 to 100. Each value in a vector is refered to as an **element** of that vector; thus the `people` vector would have 3 elements: `"Sarah"`, `"Amit"`, and `"Zhang"`.
- Importantly, all the elements in a vector need to have the same _type_ (numeric, character, logical, etc.). You can't have a vector whose elements include both numbers and character strings.
### Creating Vectors
The easiest and most common syntax for creating vectors is to use the built in `c()` function, which is used to ***c***_ombine_ values into a vector. The `c()` function takes in any number of **arguments** of the same type (separated by commas as usual), and **returns** a vector of that contains those elements:
```r
# Use the combine (`c`) function to create a vector.
people <- c("Sarah", "Amit", "Zhang")
print(people) # [1] "Sarah" "Amit" "Zhang"
numbers <- c(1, 2, 3, 4, 5)
print(numbers) # [1] 1 2 3 4 5
```
You can use the `length()` function to determine how many **elements** are in a vector:
```r
people <- c("Sarah", "Amit", "Zhang")
people_length <- length(people)
print(people_length) # [1] 3
numbers <- c(1, 2, 3, 4, 5)
print(length(numbers)) # [1] 5
```
Other functions can also help with creating vectors. For example, the `seq()` function mentioned in [chapter 6](#functions) takes 2 arguments and produces a vector of the integers between them. An _optional_ third argument specifies how many numbers to skip in each step:
```r
# Make vector of numbers 1 to 90
one_to_ninety <- seq(1, 90)
print(one_to_ninety)
# Make vector of numbers 1 to 10, counting by 2
odds <- seq(1, 10, 2)
print(odds) # [1] 1 3 5 7 9
```
- When you print out `one_to_ninety`, you'll notice that in addition to the leading `[1]` that you've seen in all printed results, there are additional bracketed numbers at the start of each line. These bracketed numbers tells you from which element number (**index**, see below) that line is showing the elements of. Thus the `[1]` means that the printed line shows elements started at element number `1`, a `[20]` means that the printed line shows elements starting at element number `20`, and so on. This is to help make the output more readable, so you know where in the vector you are when looking at in a printed line of elements!
As a shorthand, you can produce a sequence with the **colon operator** (**`a:b`**), which returns a vector `a` to `b` with the element values incrementing by `1`:
```r
one_to_ninety <- 1:90
```
Once created, you are **unable to change** the number of elements in a vector (elements cannot be added or removed). However, you can create a _new vector_ by **c**ombining a new element with an existing vector:
```r
# Use the combine (`c()`) function to create a vector.
people <- c("Sarah", "Amit", "Zhang")
# Use the `c()` function to combine the `people` vector and the name 'Josh'.
more_people <- c(people, 'Josh')
print(more_people) # [1] "Sarah" "Amit" "Zhang" "Josh"
```
## Vector Indices
Vectors are the fundamental structure for storing collections of data. Yet you often want to only work with _some_ of the data in a vector. This section will discuss a few ways that you can get a **subset** of elements in a vector.
In particular, you can refer to individual elements in a vector by their **index**, which is the number of their position in the vector. For example, in the vector:
```r
vowels <- c('a','e','i','o','u')
```
The `'a'` (the first element) is at _index_ 1, `'e'` (the second element) is at index 2, and so on.
- Note in R vector elements are indexed starting with `1`. This is distinct from most other programming languages which are _zero-indexed_ and so reference the first element at index `0`.
You can retrieve a value from a vector using **bracket notation**: you refer to the element at a particular index of a vector by writing the name of the vector, followed by square brackets (**`[]`**) that contain the index of interest:
```r
# Create the people vector
people <- c("Sarah", "Amit", "Zhang")
# access the element at index 1
first_person <- people[1]
print(first_person) # [1] "Sarah"
# access the element at index 2
second_person <- people[2]
print(second_person) # [1] "Amit"
# You can also use variables inside the brackets
last_index <- length(people) # last index is the length of the vector!
last_person <- people[last_index] # returns "Zhang"
```
- Don't get confused by the `[1]` in the printed output—it doesn't refer to which index you got from `people`, but what index in the _extracted_ result (e.g., stored in `first_person`) is being printed!
If you specify an index that is **out-of-bounds** (e.g., greater than the number of elements in the vector) in the square brackets, you will get back the value `NA`, which stands for **N**ot **A**vailable. Note that this is _not_ the _character string_ `"NA"`, but a specific logical value.
```r
vowels <- c('a','e','i','o','u')
# Attempt to access the 10th element
vowels[10] # returns NA
```
If you specify a **negative index** in the square-brackets, R will return all elements _except_ the (negative) index specified:
```r
vowels <- c("a", "e", "i", "o", "u")
# Return all elements EXCEPT that at index 2
all_but_e <- vowels[-2]
print(all_but_e) # [1] "a" "i" "o" "u"
```
### Multiple Indices
Remember that in R, **everything is a vector**. This means that when you put a single number inside the square brackets, you're actually putting a _vector with a single element in it_ into the brackets So what you're really doing is specifying a **vector of indices** that you want R to extract from the vector. As such, you can put a vector of any length inside the brackets, and R will extract _all_ the elements with those indices from the vector (producing a **subset** of the vector elements):
```r
# Create a `colors` vector
colors <- c("red", "green", "blue", "yellow", "purple")
# Vector of indices to extract
indices <- c(1, 3, 4)
# Retrieve the colors at those indices
extracted <- colors[indices]
print(extracted) # [1] "red" "blue" "yellow"
# Specify the index array anonymously
others <- colors[c(2, 5)]
print(others) # [1] "green" "purple"
```
It's incredibly common to use the **colon operator** to quickly specify a range of indices to extract:
```r
# Create a `colors` vector
colors <- c("red", "green", "blue", "yellow", "purple")
# Retrieve values in positions 2 through 5
colors[2:5] # [1] "green" "blue" "yellow" "purple"
```
This easily reads as _"a vector of the elements in positions 2 through 5"_.
## Modifying Vectors
While you are unable to change the number of elements within a vector, you _are_ able to change the individual values within a vector. To achieve this, put the extracted _subset_ on the **left-hand side** of the assignment operator, and then assign the element a new value:
```r
# Create a vector of school supplies
school_supplies <- c("Backpack", "Laptop", "Pen")
# Replace 'Pen' (element at index 3) with 'Pencil'
school_supplies[3] <- "Pencil"
```
And of course, there's no reason that you can't select multiple elements on the left-hand side, and assign them multiple values. The assignment operator is also _vectorized_!
```r
# Create a vector of school supplies
school_supplies <- c("Backpack", "Laptop", "Pen")
# Replace 'Laptop' with 'Tablet', and 'Pen' with 'Pencil'
school_supplies[c(2, 3)] <- c("Tablet", "Pencil")
```
As a more useful example, imagine you had a vector of values in which you wanted to replace all numbers greater that 10 with the number 10 (to "cap" the values). Because the assignment operator is vectorized, you can leverage _recycling_ to assign a single value to each element that has been _filtered_ from the vector:
```r
# Element of values
v1 <- c(1, 5, 55, 1, 3, 11, 4, 27)
# Replace all values greater than 10 with 10
v1[v1 > 10] <- 10 # returns 1, 5, 10, 1, 3, 10, 4, 10
```
In this example, the number `10` get recycled for each element in which `v1` is greater than 10 (`v1[v1 > 10]`). Presto!
## Vector Filtering
In the above section, you used a vector of indices (_numeric_ values) to retrieve a subset of elements from a vector. Alternatively, you can put a **vector of logical (boolean) values** inside the square brackets to specify which ones you want to extract (`TRUE` in the _corresponding position_ means extract, `FALSE` means don't extract):
```r
# Create a vector of shoe sizes
shoe_sizes <- c(7, 6.5, 4, 11, 8)
# Vector of elements to extract
filter <- c(TRUE, FALSE, FALSE, TRUE, TRUE)
# Extract every element in an index that is TRUE
shoe_sizes[filter] # [1] 7 11 8
```
R will go through the boolean vector and extract every item at the same position as a `TRUE`. In the example above, since `filter` is `TRUE` and indices 1, 4, and 5, then `shoe_sizes[filter]` returns a vector with the elements from indices 1, 4, and 5.
This may seem a bit strange, but it is actually incredibly powerful because it lets you select elements from a vector that _meet a certain criteria_ (called **filtering**). You perform this _filtering operation_ by first creating a vector of boolean values that correspond with the indices meeting that criteria, and then put that filter vector inside the square brackets:
```r
# Create a vector of shoe sizes
shoe_sizes <- c(7, 6.5, 4, 11, 8)
# Create a boolean vector that indicates if a shoe size is greater than 6.5
shoe_is_big <- shoe_sizes > 6.5 # T, F, F, T, T
# Use the `shoe_is_big` vector to select large shoes
big_shoes <- shoe_sizes[shoe_is_big] # returns 7, 11, 8
```
The magic here is that you are once again using _recycling_: the relational operator `>` is _vectorized_, meaning that the shorter vector (the `6.5`) is recycled and applied to each element in the `shoe_sizes` vector, thus producing the boolean vector that you want!
You can even combine the second and third lines of code into a single statement. You can think of the following statement as saying "shoe_sizes **where** shoe_sizes is greater than 6.5":
```r
# Create a vector of shoe sizes
shoe_sizes <- c(7, 6.5, 4, 11, 8)
# Select shoe sizes that are greater than 6.5
shoe_sizes[shoe_sizes > 6.5] # returns 7, 11, 8
```
This is a valid statement because the equality inside of the square-brackets (`shoe_sizes > 6.5`) is evaluated first, producing the boolean vector which is then used to filter the `shoe_sizes` vector.
This kind of filtering is crucial for being able to ask real world questions of datasets.
## Vectorized Operations
When performing operations (such as mathematical operations `+`, `-`, etc.) on vectors, the operation is applied to vector elements **member-wise**. This means that each element from the first vector operand is modified by the element in the **same corresponding position** in the second vector operand, in order to determine the value _at the corresponding position_ of the resulting vector. E.g., if you want to add (`+`) two vectors, then the value of the first element in the result will be the sum (`+`) of the first elements in each vector, the second element in the result will be the sum of the second elements in each vector, and so on.
```r
# Create two vectors to combine
v1 <- c(1, 1, 1, 1, 1)
v2 <- c(1, 2, 3, 4, 5)
# Create arithmetic combinations of the vectors
v1 + v2 # returns 2, 3, 4, 5, 6
v1 - v2 # returns 0, -1, -2, -3, -4
v1 * v2 # returns 1, 2, 3, 4, 5
v1 / v2 # returns 1, .5, .33, .25, .2
# Add a vector to itself (why not?)
v3 <- v2 + v2 # returns 2, 4, 6, 8, 10
# Perform more advanced arithmetic!
v4 <- (v1 + v2) / (v1 + v1) # returns 1, 1.5, 2, 2.5, 3
```
While we can't apply mathematical operators (namely, `+`) to combine vectors of character strings, we can use functions like `paste()` to concatenate the elements of two vectors.
```r
colors <- c("Green", "Blue")
spaces <- c("sky", "grass")
# Note: look up the `paste0()` function if it's not familiar!
band <- paste0(colors, spaces) # returns "Greensky", "Bluegrass"
# http://greenskybluegrass.com/
```
Notice the same _member-wise_ combination is occurring: the `paste0()` function is applied to the first elements, then to the second elements, and so on.
### Recycling
**Recycling** refers to what R does in cases when there are an unequal number of elements in two operand vectors. If R is tasked with performing a vectorized operation with two vectors of unequal length, it will reuse (_recycle_) elements from the shorter vector. For example:
```r
# Create vectors to combine
v1 <- c(1, 3, 5)
v2 <- c(1, 2)
# Add vectors
v3 <- v1 + v2 # returns (2, 5, 6)
```
In this example, R first combined the elements in the first position of each vector (`1+1=2`). Then, it combined elements from the second position (`3+2=5`). When it got to the third element (which only was present in `v1`), it went back to the **beginning** of `v2` to select a value, yielding `5+1=6`.
- Recycling will occur no matter if the longer vector is the first or second operand.
- R may provide a warning message, notifying you that the vectors are of different length. This warning doesn't necessarily mean you did something wrong, but you should pay attention to it because it may be indicative of an error (i.e., you thought the vectors were of the same length, but made a mistake somewhere).
### Everything is a Vector!
What happens if you try to add a vector and a "regular" single value (a **scalar**)?
```r
# create vector of numbers 1 to 5
v1 <- 1:5
result <- v1 + 4 # add scalar to vector
print(result) # [1] 5 6 7 8 9
```
As you can see (and probably expected), the operation added `4` to every element in the vector.
The reason this sensible behavior occurs is because, in truth, **everything in R is a vector**. Even when you thought you were creating a single value (a scalar), you were actually just creating a vector with a single element (length 1). When you create a variable storing the number `7` (with `x <- 7`), R creates a vector of length 1 with the number `7` as that single element.
- This is why R prints the `[1]` in front of all results: it's telling you that it's showing a vector (which happens to have 1 element) starting at element number 1.
- This is also why you can't use the `length()` function to get the length of a character string; it just returns the length of the array containing that string (`1`). Instead, use the `nchar()` function to get the number of characters in a character string.
```r
# Create a vector of length 1 in a variable x
x <- 7 # equivalent to `x <- c(7)`
# Print out x: R states the vector index (1) in the console
print(x) # [1] 7
```
Thus when you add a "scalar" such as `4` to a vector, what you're really doing is adding a vector with a single element `4`. As such the same _recycling_ principle applies, and that single element is "recycled" and applied to each element of the first operand.
### Vectorized Functions
> _Vectors In, Vector Out_
Because _everything is a vector_, it means that pretty much every function you've used so far has actually applied to vectors, not just to single values. These are referred to as **vectorized functions**, and will run significantly faster than non-vector approaches. You'll find that functions work the same way for vectors as they do for single values, because single values are just instances of vectors!
- _Fun fact:_ The mathematical operators (e.g., `+`) are actually functions in R that take 2 arguments (the operands). The mathematical notation we're used to using is just a shortcut.
```r
# these two lines of code are the same:
x <- 2 + 3 # add 2 and 3
x <- '+'(2, 3) # add 2 and 3
```
This means that you can use any function on a vector, and it will act in the same **vectorized**, _member-wise_ manner: the function will result in a new vector where the function's transformation has been applied to each individual element in order.
For example consider the `round()` function described in the previous chapter. This function rounds the given argument to the nearest whole number (or number of decimal places if specified).
```r
# round number to 1 decimal place
round(1.67, 1) # returns 1.6
```
But recall that the `1.6` in the above example is _actually a vector of length 1_. If we instead pass a vector as an argument, the function will perform the same rounding on each element in the vector.
```r
# Create a vector of numbers
nums <- c(3.98, 8, 10.8, 3.27, 5.21)
# Perform the vectorized operation
whole_nums <- round(nums, 1)
# Print the results (each element is rounded)
print(whole_nums) # [1] 4.0 8.0 10.8 3.3 5.2
```
This vectorization process is ___extremely powerful___, and is a significant factor in what makes R an efficient language for working with large data sets (particularly in comparison to languages that require explicit iteration through elements in a collection). Thus to write really effective R code, you'll need to be comfortable applying functions to vectors of data, and getting vectors of data back as results.
<p class="alert">Just remember: _when you use a function on a vector, you're using that function **on each item** in the vector_!</p>
## Resources {-}
- [R Tutorial: Vectors](http://www.r-tutor.com/r-introduction/vector)