4  Vectors

Learn R live!

Prefer to learn via live instruction? Register for my Introduction to R for Data Analysis seminar via Instats on January 15-16 2025.

4.1 Defining a vector

While being able to store numbers and text in a variable, such as x <- 12, is super neat, the real power of variables is being able to store a wide variety of objects, including an entire dataset, model, or even a data visualization!

However, before we try to create an object containing an entire dataset, let’s start with a variable that contains just a collection of values, such as might appear in a single column of a dataset.

The kind of object that contains a collection of values is called a vector. Let’s create a vector that contains the ages of 5 people and store it in a variable called age:

age <- c(12, 19, 22, 35, 18)

Just like our variables in the previous chapters, we can look at the contents of age by typing its name:

age
[1] 12 19 22 35 18

But unlike our previous variables, age contains many values, and this is because age is a vector, which is defined by “concatenating” values together using the c() function.

Note that the [1] at the beginning of the output is just telling you that the first value is at index position/location 1 (i.e., that it is the first entry). If our vector is so long that its output spills onto multiple lines, such as the vector below, notice that the second line in the output has a different number inside the square parentheses. This is just telling you which index position the first entry on the second line has (and it may change based on the width of your window when the code was run).

long_age <- c(12, 19, 22, 35, 18, 44, 23, 56, 23, 12, 18, 19, 50, 60, 77, 54, 
              34, 66, 34, 32, 19, 20, 21, 18, 19, 72, 27, 43, 63, 23, 12, 18, 
              19, 50, 60, 77, 54)
long_age
 [1] 12 19 22 35 18 44 23 56 23 12 18 19 50 60 77 54 34 66 34 32 19 20 21 18 19
[26] 72 27 43 63 23 12 18 19 50 60 77 54

The c() function asks R to place all of the values provided inside the parentheses of c(), which are separated by commas, into a single vector object.

You might think that when we apply class() to our age vector object, it would return “vector”. However, the type or class of a vector is actually just the type or class of the values it contains, which in this case, is “numeric”

class(age)
[1] "numeric"

This means that if we had created a vector of names, such as the one below:

names <- c("Dean", "Xiao", "Sara", "Ravi", "Maya")
names
[1] "Dean" "Xiao" "Sara" "Ravi" "Maya"

Then our names vector object will have class “character”:

class(names)
[1] "character"

Vectors are great. Rather than having to carry around all of my individual numbers and words individually, I can put them all into a little vector “bag” and carry them around together.

However, vectors are a little bit particular. Let’s try and create a vector that contains multiple different types of values, such as numbers and text:

multi_vec <- c(1, 9, "banana", 10, -1)
multi_vec
[1] "1"      "9"      "banana" "10"     "-1"    

What class/type do you think this multi_vec vector will have? Take a close look at the values in the multi_vec output above. Notice the quotes around the numbers. Let’s check the class of multi_vec:

class(multi_vec)
[1] "character"

Interesting. multi_vec is a character vector, despite the fact that most of the values used to create it were numbers.

This is because vectors can only contain values of a single type.

R will let you create a vector using values of multiple different types (such as numbers and characters), but in the actual vector object that is created, all of the values will be converted to the same type, in this example, that type was “character”.

What do you think will happen if we try to create a vector with numeric and logical values (TRUE/FALSE) values? Below I try to combine some numbers with a TRUE and a FALSE into the same vector.

multi_vec2 <- c(1, 5, TRUE, FALSE, -9)

Notice how the output when I print the name of the object differs from the object I defined above:

multi_vec2
[1]  1  5  1  0 -9

What has happened here? Just like R converted my numbers to a character when a character value was present in the vector, here, R has converted my logical values (my TRUE and FALSE values) to numbers (corresponding to 1 and 0, respectively).

How can you tell what type a vector will have when it is created using values of various different types? It turns out that there is a hierarchy of types:

Character > Numeric > Logical

This doesn’t mean that characters are better than numerics and logicals, but rather this means that if a character value is present among the values that define the vector, then all values in the vector will be converted to the character type. If there are no characters being used to define the vector, but there are numeric values and logical values, then all of the values will be converted to the numeric type.

Before you run the code below, predict what vector will be created from the code below. Consider the type hierarchy above.

vector_example <- c(TRUE, 4, "hello", FALSE, 0)

Since the vector definition includes a character value, all values in the resulting vector have a character type (notice the quotes)

vector_example <- c(TRUE, 4, "hello", FALSE, 0)
vector_example
[1] "TRUE"  "4"     "hello" "FALSE" "0"    
class(vector_example)
[1] "character"

4.2 Working with vectors: vectorization

While it’s super neat that we can collect all of our numbers and words in a single vector object (although no mixing of words and numbers), the actual cool thing about vectors is that it makes it really easy to do computations on all of our values at once.

If we define our age vector below:

age <- c(12, 18, 22, 21, 17)

Then we can demonstrate a really neat property of vectors: if I subtract 1 from the vector object age, R will subtract 1 from every value in the vector at once:

age - 1
[1] 11 17 21 20 16

Let’s create an entirely new vector object, that I’m going to creatively call age2, which contains the original age vector multiplied by 2.

age2 <- age * 2

If we want to look at what values age2 contains, we can print out its name, and lo and behold, all of the values in age2 correspond to the original values in age, multiplied by 2:

age2
[1] 24 36 44 42 34

The fact that mathematical operations applied to a vector are applied separately to each individual value in the vector is called vectorization.

While this might not seem that cool to you. Trust me when I say that this 100% is cool. Imagine how tired your fingers would get if you had to subtract 1 from every individual value in a vector containing 1000 values. With vectorization, I just have to subtract 1 from the vector object itself, and I’m done.

So now that we have two age vectors, age and age2, which are both printed below:

age
[1] 12 18 22 21 17
age2
[1] 24 36 44 42 34

What do you think will happen if I try to add these two vectors together?

age2 + age
[1] 36 54 66 63 51

Because vectors are vectorized, the entries were added element-wise. This means that the first value in age was added to the first value in age2, and similarly for the second value, and so on.

Note that in the age2 + age computation above, I printed out the resulting vector, but I did not save this vector as an object. Having been computed, the age2 + age vector has now been lost to the ether. If I wanted to use this resulting vector for something, I would need to save it as a new variable (such as age3 <- age2 + age).

Since we can add vectors together, it follows that we can probably also subtract them from one another and multiply them by one another, and all of these operations will happen element-wise. For example, we can divide age2 by age, and we will get a vector containing 5 2s, because each entry in age2 is twice the corresponding entry in age:

age2 / age
[1] 2 2 2 2 2

In this example, both age and age2 have the same length. That is, they have the same number of entries.

What do you think will happen if we try to do a computation with vectors of different lengths? Let’s try to subtract a vector of length 2 (c(1, 2)) from age, which has length 5:

age - c(1, 2)
Warning in age - c(1, 2): longer object length is not a multiple of shorter
object length
[1] 11 16 21 19 16

Interestingly, it worked, but we got a warning message “longer object length is not a multiple of shorter object length”. Take a look at the output of the code above. Can you figure out what R did here?

R is being very presumptuous. Without even bothering to ask me, it went ahead and repeated the values in the shorter vector, c(1, 2), to match the length of the longer vector, age, i.e., until it gets to 5 values in total, so it is essentially doing this:

age - c(1, 2, 1, 2, 1)
[1] 11 16 21 19 16

Personally, I’d prefer if R gave me an error when I try to do mathematical operations with vectors of different lengths. But unfortunately for me, I didn’t write the R programming language, I just use it.

To be fair, R did provide a warning that I was trying to do a computation with vectors of different lengths. But it’s really easy to unintentionally ignore warnings.

If you ever see this warning, it probably means that you’ve made a mistake somewhere. I can guarantee that you almost never actually want to do mathematical operations with vectors of different lengths.

In summary, my advice is don’t ignore the warning message “longer object length is not a multiple of shorter object length”. Check your lengths and check your code output!

Speaking of “checking your lengths”, it might be helpful if I told you how to do that! You can compute the length of a vector by applying the length() function to it:

length(age)
[1] 5

4.2.1 Vectorized logical operations

Do you remember when we asked questions about the values we stored in our variables/objects, like x == 1? Well, it turns out that we can ask the same questions of vectors! And, you guessed it, those questions will be asked element-wise.

Let’s keep working with our age vector:

age
[1] 12 18 22 21 17

If we ask “which age entries are greater or equal to 18” using the code below:

age >= 18
[1] FALSE  TRUE  TRUE  TRUE FALSE

This question gets asked separately for every entry in age. The resulting logical vector above is TRUE for the age entries that are 18 or above, and is FALSE for the age entries that are less than 18.

Let’s ask another question: “which age entries are equal to 17”?

age == 17
[1] FALSE FALSE FALSE FALSE  TRUE

It looks like only the last one is.

What about “which age entries are not equal to 21”?

age != 21
[1]  TRUE  TRUE  TRUE FALSE  TRUE

What if we want to ask which age entries are equal to either 17 or 18? The natural thing to try is:

age == c(17, 18)
Warning in age == c(17, 18): longer object length is not a multiple of shorter
object length
[1] FALSE  TRUE FALSE FALSE  TRUE

But notice our longer object length is not a multiple of shorter object length warning!

If we take a look at age again,

age
[1] 12 18 22 21 17

It looks like age == c(17 18) gave us the right answer (as in, we got TRUE for the second and fifth entries), but I never like to ignore a “longer object length is not a multiple of shorter object length” warning message.

Since the code age == c(17, 18) worked, it should probably also work if we switch the order of 18 and 17 in our question, right?

age == c(18, 17)
Warning in age == c(18, 17): longer object length is not a multiple of shorter
object length
[1] FALSE FALSE FALSE FALSE FALSE

This time we still get some output, along with our “longer object length is not a multiple of shorter object length” warning, but the answer is wrong. All of the entries in the output vector are FALSE.

This is because R is doing that pesky recycling thing again. This question is equivalent to:

age == c(18, 17, 18, 17, 18)
[1] FALSE FALSE FALSE FALSE FALSE

And the question is being asked element-wise (is the first entry equal to 18? Is the second entry equal to 17? Is the third entry equal to 18?). The only reason we got the correct answer the first time is because we got lucky with our recycling.

The moral of the story is: don’t ignore the warning message “longer object length is not a multiple of shorter object length”. Check your lengths!

4.2.2 The %in% operator

Okay, so if age == c(17, 18) isn’t how we ask the question of which age entries are equal to 17 or 18, how do we ask that question?

We are going to use a new operator, %in%. To use %in%, just replace == in the question above, with %in%!

# use %in% to ask which entries in age are equal to 17 or 18
age %in% c(17, 18) 
[1] FALSE  TRUE FALSE FALSE  TRUE

Et voila! This time it tells us that the second and fifth entries are equal to either 17 or 18, and we didn’t get any warnings! Yay!

4.3 Summary functions for vectors

So I showed you earlier that you can use the length() function to compute the number of values in a vector, but this is just one of many functions you can use to summarize a vector.

For example, the sum() function can be used to add up all the entries in a (numeric) vector:

sum(age)
[1] 90

The mean() function computes the mean/average:

mean(age)
[1] 18

The median() function computes the median:

median(age)
[1] 18

The var() function computes the variance:

var(age)
[1] 15.5

The sd() function computes the standard deviation:

sd(age)
[1] 3.937004

The function length() tells you how many entries the vector contains:

length(age)
[1] 5

The min() function tells you the smallest value:

min(age)
[1] 12

And the max function tells you the biggest value:

max(age)
[1] 22

And we can combine some of the super fun logical stuff from above with sum() to compute even more interesting summaries.

First, note that when you apply sum() (or mean()) to a vector of logical values, it treats FALSE as 0 and TRUE as 1. So when you apply sum() to a logical vector, it adds up the number of TRUE values:

# compute the number of TRUE values 
sum(c(TRUE, FALSE, FALSE, TRUE))
[1] 2

So we can use this to do things like add up the number of values in age that are either 17 or 18:

sum(age %in% c(17, 18))
[1] 2

Or add the number of values in age that are strictly greater than 15:

sum(age > 15)
[1] 4

Try to use the functions above to compute the proportion of people whose age is strictly greater than 15

Consider using the sum() function and the length() function.

sum(age > 15) / length(age)
[1] 0.8

4.4 Extracting information from vectors

We know how to put values into a vector (i.e., using c()), but how do we get them out again?

To extract values from a vector, you can type the name of the vector that you want to extract the values from, followed by some square parentheses [], inside which you place the numeric location (index) of the value you want to extract.

Let’s keep working with age:

age
[1] 12 18 22 21 17

To extract the first entry from age:

age[1]
[1] 12

To extract the fourth entry from age:

age[4]
[1] 21

If you want to extract the final entry in a vector and you don’t immediately know its length, you can do something clever like this:

age[length(age)]
[1] 17

Why does this work? Remember that length(age) tells you how many values there are in age (i.e., 5), and so this is equivalent to age[5], which will extract the final value from the age vector.

4.4.1 Removing a value from a vector

If I wanted to extract the first entry from age, I would write, age[1]. This is actually essentially creating a new vector that just consists of the first value in age (although I haven’t saved this vector anywhere).

If I wanted to instead create a new vector that removed this first entry, I would write

# remove the first entry from age
age[-1]
[1] 18 22 21 17

So age[1] extracts the first entry from age and age[-1] removes the first entry from age.

Keep in mind that none of these operations so far have modified the original age object:

age
[1] 12 18 22 21 17

age[1] prints the result of extracting the first entry from age, but I am not saving this result, nor am I overwriting our age vector with this value. Remember that the output of your code is only saved when you assign the result of the computation to something using <-!

Remove the fourth entry from age

age <- c(12, 18, 22, 21, 17)
age[-4]
[1] 12 18 22 17

4.4.2 Extracting/removing multiple entries from a vector

So far we have just extracted and removed a single entry from age at a time. But often, we want to be able to extract or remove multiple entries at once. That is, I want to provide multiple values inside my square parentheses [ ], but they only accept one value!

Let’s quickly remind ourselves of what age contains:

age
[1] 12 18 22 21 17

If I try to provide two values inside my [ ] parentheses, I get an error. For example, below, I try to extract both the first and third entries (12 and 22) from age at once by just providing two numbers inside the square parentheses:

age[1, 3]
Error in age[1, 3]: incorrect number of dimensions

But I got an error :(. The error "incorrect number of dimensions" is telling me that it only wants one object, not two inside the square parentheses!

So I need to provide two position values (1 and 3), but I can only provide one object inside. How could I create one object that contains two values? One object… two values… Hmmmmmmmm. Have you figured it out yet? Why don’t you put the two values inside a vector! Wow! Neat idea!

Let’s try and extract the first and third entries from age at once, by providing a vector c(1, 3) inside the square parentheses:

age[c(1, 3)]
[1] 12 22

It worked!

Maybe we can also remove the first and third entries by providing the negative of this vector:

age[-c(1, 3)]
[1] 18 21 17

That worked too! Vectors are great.

4.5 Definining integer sequences

What if you wanted to define a really long vector of sequential integers like:

my_long_vector <- c(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)
my_long_vector
  [1] 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
 [19] 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
 [37] 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
 [55] 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
 [73] 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
 [91] 191 192 193 194 195 196 197 198 199 200

Writing this out made my fingers really tired. And if you’ve learned anything about me so far, you’ll know how much I hate it when my fingers get tired.

Fortunately, there’s a better way. If I want to define a vector containing a sequence of consecutive integers like in my_long_vector, I can use the : syntax. For example, to create the vector c(1, 2, 3, 4), I could write:

1:4
[1] 1 2 3 4

Note that I haven’t saved this vector (I just wrote the code to create it and then the result was printed and subsequently lost to the ether), but I could if I wanted to. Below, I save the above vector in an object called vector1to4:

vector1to4 <- 1:4

And then I can access this vector by writing its name:

vector1to4
[1] 1 2 3 4

The syntax to create a sequential vector of integers is start:stop. So if 1:4 created the vector c(1, 2, 3, 4), how might you create the long vector I saved in my_long_vector above? Well the starting value is 101 and the last (stop) value is 200, so maybe we can try 101:200:

101:200
  [1] 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
 [19] 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
 [37] 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
 [55] 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
 [73] 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
 [91] 191 192 193 194 195 196 197 198 199 200

Perfecto!

The cool thing about this is that we can use it to extract segments of a vector, for instance, to extract the first four entries of age, we could write

age[1:4]
[1] 12 18 22 21

4.6 Logical subsetting

Sometimes you might want to extract all the entries from a vector that satisfy a certain condition. To do that, you first need to understand how to use a logical vector to extract values.

If I provide a vector of TRUEs and FALSEs inside the square parentheses, R will extract all the values whose corresponding entry in the logical vector are TRUE.

For example, the following code will extract the first, fourth, and fifth entries:

age[c(TRUE, FALSE, FALSE, TRUE, TRUE)]
[1] 12 21 17

OK. So I would never actually write out such a vector, because I have a life, but remember when we asked logical questions of our vectors, such as, which entries in age are greater than or equal to 18?

age >= 18
[1] FALSE  TRUE  TRUE  TRUE FALSE

This creates a logical vector for us and the TRUE values correspond to the values in the vector for which the condition is true. Do you see where I’m going with this?

If you want to extract all of the values in a vector for which a logical condition is true, you can provide the logical condition inside the square parentheses of the vector!

The following code will extract the values in age that are all greater or equal to 18:

age[age >= 18]
[1] 18 22 21

This is great for simple conditions, but what about more complex conditions, such as ages that are at least 17 but less than 20? Unfortunately, the R code that would correspond to the mathematical syntax \(17 \leq x \leq 20\) doesn’t work in R:

17 <= age < 20
Error: <text>:1:11: unexpected '<'
1: 17 <= age <
              ^

Instead, we have to combine multiple conditions in R, using | if we want either condition to be true (the logical “OR”) and & if we want both conditions to be true (the logical “AND”).

The condition that the age is at least 17 but less than 20 is the combination of the two conditions age >= 17 and age < 20, and we need both of these things to be true so we can write

# age at least 17 and less than 20
(age >= 17) & (age < 20)
[1] FALSE  TRUE FALSE FALSE  TRUE

Combining conditions with an & will only be TRUE if both conditions are TRUE.

Let’s use this “AND” condition to extract all of the entries in age that are both greater or equal to 17 and less than 20.

age[(age >= 17) & (age < 20)]
[1] 18 17

On the other hand, combining conditions with an | “OR” operator will be TRUE if either TRUE (even if the other one is FALSE).

So for example, the ages that are either less than 16 or greater than 20 are the first, third, and fourth entries

(age < 16) | (age > 20)
[1]  TRUE FALSE  TRUE  TRUE FALSE

And we can use this | operator to extract all of the entries in age that are either less than 16 or greater than 20:

age[(age <= 16) | (age > 20)]
[1] 12 22 21

Sorry if your brain hurts.

Let’s practice a little.

Here is a new vector, vec.

vec <- c(4, 19, 2, 2, 3, 90, 55, 12)

Extract the entries that are less than 10

vec[vec < 10]
[1] 4 2 2 3

Extract the entries of vec that are less than 25 but greater than 10

Since I need both vec < 25 and vec > 10 to be TRUE, this involves an & statement:

vec[(vec < 25) & (vec > 10)]
[1] 19 12

Extract the entries of vec that are either less than 10 or equal to 55

Since I only need either vec < 10 and vec == 55 to be TRUE, this involves an | statement:

vec[(vec < 10) | (vec == 55)]
[1]  4  2  2  3 55

4.7 Named vectors

If we wanted each entry in age to have its own name, we could use the names() function.

Note that names(age) extracts an attribute of age (its names, which are currently nonexistent), and by assigning names(age) to something, we can update the names.

Below, we update the names of the entries in age to be “Dean”, “Xiao”, “Sara”, “Ravi”, and “Maya”, respectively.

names(age) <- c("Dean", "Xiao", "Sara", "Ravi", "Maya")

Note that this does modify the age object directly (specifically, it modifies the names of age through assignment <-):

age
Dean Xiao Sara Ravi Maya 
  12   18   22   21   17 

While you can define a vector and then update its names later, you can alternatively create the names when you initially create the vector using the syntax below.

age <- c("Dean" = 12, "Xiao" = 18, "Sara" = 22, "Ravi" = 21, "Maya" = 17)
age
Dean Xiao Sara Ravi Maya 
  12   18   22   21   17 

Take a look at the output of this “named vector”. How does it look different from the original unnamed age vector? The name for each entry appears above the value, and the [1] at the beginning of the vector that denotes the first entry is gone! I have no explanation for why this second thing happens.

The cool thing about named vectors is that you can extract an entry from a vector using its name. For example, if I just wanted Ravi’s age, I could write:

age["Ravi"]
Ravi 
  21 

Note that the name of the entry must be a character string, i.e., I have to have quotes around "Ravi".

I can also extract several entries from the vector using a vector of the names I want, just as I did with numbers representing the index positions I wanted to extract:

age[c("Maya", "Ravi")]
Maya Ravi 
  17   21 

4.8 Factors

Before moving on to actually working with data (yay!), I want to talk briefly about factors.

Factors are essentially vectors coupled with a set of allowed values. For example, you will often find states (e.g., US states CA, OR, NY, etc) stored as a factor since there are a pre-defined set of states.

As an example, let’s create a character vector of 10 Australian states (where some states appear more than once–there are only 7 states total):

australia_states <- c("New South Wales", "New South Wales", "Queensland", "Tasmania", "ACT", "South Australia", "Western Australia", "Northern Territory", "New South Wales", "Queensland", "ACT")
australia_states
 [1] "New South Wales"    "New South Wales"    "Queensland"        
 [4] "Tasmania"           "ACT"                "South Australia"   
 [7] "Western Australia"  "Northern Territory" "New South Wales"   
[10] "Queensland"         "ACT"               

And let’s create a factor variable version of this vector using the factor() function:

australia_states_fct <- factor(australia_states)
australia_states_fct
 [1] New South Wales    New South Wales    Queensland         Tasmania          
 [5] ACT                South Australia    Western Australia  Northern Territory
 [9] New South Wales    Queensland         ACT               
7 Levels: ACT New South Wales Northern Territory ... Western Australia

What are two differences between the output of the character vector, australia_states and the factor australia_states_fct?

  1. The factor entries are not surrounded by quotes

  2. Underneath the factor output some text says 7 Levels: ACT New South Wales ... Western Australia – these list the unique levels in the vector.

Remember that we couldn’t convert a character vector to a numeric vector:

as.numeric(australia_states)
Warning: NAs introduced by coercion
 [1] NA NA NA NA NA NA NA NA NA NA NA

It turns out that we can convert a factor to a numeric vector:

as.numeric(australia_states_fct)
 [1] 2 2 4 6 1 5 7 3 2 4 1

But what is it doing? It replaces all instances of the first level, ACT, with 1, all instances of the second level, New South Wales, with 2, etc. This can be very handy, but also very dangerous.

To demonstrate why, let’s create a factor containing numbers (factors are not just reserved for text!)

fct <- factor(c(5, 1, 1, 3, 6, 5, 5, 6, 1))
fct
[1] 5 1 1 3 6 5 5 6 1
Levels: 1 3 5 6

Notice that the factor levels are unique (i.e,. 1 only appears once in the levels, even though there are three 1s in the factor itself)

If I try to convert the factor to a numeric variable, the numbers get all messed up:

as.numeric(fct)
[1] 3 1 1 2 4 3 3 4 1

What R is doing here is replacing the first level entry, 1, with 1 (so the 1s remain untouched), it is replacing the second level entry, 3, with 2, and replacing the third level entry, 5, with 3, and so on.

It’s hard to give concrete advice about factors at this stage because they only really become relevant when you start doing fancy statistical modeling or creating sophisticated graphics using categorical data. For the most part, unless you are using a model that requires factor variables, your life will be slightly easier if you store your categorical/text information as character vectors and your numeric information as numeric vectors rather than factors. Once you get to the modeling stage, you’ll see that 80% of the functions that require your categorical data to be a factor will automatically convert them to a factor for you anyway.