# R Basics continued - factors and data frames

Estimated time: 90 minutes

## Overview

### Questions

• How do I get started with tabular data (e.g. spreadsheets) in R?
• What are some best practices for reading data into R?
• How do I save tabular data generated in R?

### Objectives

• Explain the basic principle of tidy datasets
• Be able to load a tabular dataset using base R functions
• Be able to determine the structure of a data frame including its dimensions and the datatypes of variables
• Be able to subset/retrieve values from a data frame
• Understand how R may coerce data into different modes
• Be able to change the mode of an object
• Understand that R uses factors to store and manipulate categorical data
• Be able to manipulate a factor, including subsetting and reordering
• Be able to apply an arithmetic function to a data frame
• Be able to coerce the class of an object (including variables in a data frame)
• Be able to import data from Excel
• Be able to save a data frame as a delimited file

## Working with spreadsheets (tabular data)

A substantial amount of the data we work with in genomics will be tabular data, this is data arranged in rows and columns - also known as spreadsheets. We could write a whole lesson on how to work with spreadsheets effectively (actually we did). For our purposes, we want to remind you of a few principles before we work with our first set of example data:

1) Keep raw data separate from analyzed data

This is principle number one because if you can’t tell which files are the original raw data, you risk making some serious mistakes (e.g. drawing conclusion from data which have been manipulated in some unknown way).

The simplest principle of Tidy data is that we have one row in our spreadsheet for each observation or sample, and one column for every variable that we measure or report on. As simple as this sounds, it’s very easily violated. Most data scientists agree that significant amounts of their time is spent tidying data for analysis. Read more about data organization in our lesson and in this paper.

3) Trust but verify

Finally, while you don’t need to be paranoid about data, you should have a plan for how you will prepare it for analysis. This a focus of this lesson. You probably already have a lot of intuition, expectations, assumptions about your data - the range of values you expect, how many values should have been recorded, etc. Of course, as the data get larger our human ability to keep track will start to fail (and yes, it can fail for small data sets too). R will help you to examine your data so that you can have greater confidence in your analysis, and its reproducibility.

### Tip: Keep your raw data separate

When you work with data in R, you are not changing the original file you loaded that data from. This is different than (for example) working with a spreadsheet program where changing the value of the cell leaves you one “save”-click away from overwriting the original file. You have to purposely use a writing function (e.g. write.csv()) to save data loaded into R. In that case, be sure to save the manipulated data into a new file. More on this later in the lesson.

## Importing tabular data into R

There are several ways to import data into R. For our purpose here, we will focus on using the tools every R installation comes with (so called “base” R) to import a comma-delimited file containing the results of our variant calling workflow. We will need to load the sheet using a function called read.csv().

### Exercise: Review the arguments of the read.csv() function

Before using the read.csv() function, use R’s help feature to answer the following questions.

Hint: Entering ‘?’ before the function name and then running that line will bring up the help documentation. Also, when reading this particular help be careful to pay attention to the ‘read.csv’ expression under the ‘Usage’ heading. Other answers will be in the ‘Arguments’ heading.

1. What is the default parameter for ‘header’ in the read.csv() function?

2. What argument would you have to change to read a file that was delimited by semicolons (;) rather than commas?

3. What argument would you have to change to read file in which numbers used commas for decimal separation (i.e. 1,00)?

4. What argument would you have to change to read in only the first 10,000 rows of a very large file?

1. The read.csv() function has the argument ‘header’ set to TRUE by default, this means the function always assumes the first row is header information, (i.e. column names)

2. The read.csv() function has the argument ‘sep’ set to “,”. This means the function assumes commas are used as delimiters, as you would expect. Changing this parameter (e.g. sep=";") would now interpret semicolons as delimiters.

3. Although it is not listed in the read.csv() usage, read.csv() is a “version” of the function read.table() and accepts all its arguments. If you set dec="," you could change the decimal operator. We’d probably assume the delimiter is some other character.

4. You can set nrow to a numeric value (e.g. nrow=10000) to choose how many rows of a file you read in. This may be useful for very large files where not all the data is needed to test some data cleaning steps you are applying.

Hopefully, this exercise gets you thinking about using the provided help documentation in R. There are many arguments that exist, but which we wont have time to cover. Look here to get familiar with functions you use frequently, you may be surprised at what you find they can do.

The reason for this is because read.csv is actually a short cut for read.table("file.csv", sep = ","). You can see in the help documentation that there are several additional variations of read.table, such as read.csv2 to read tables separated by ; and read.delim to read in tables separated by \t (tabs). If you know how your table is separated, you can use one of the provided short cuts, but case you run into an unconventional separator you are now equipped with the knowledge to define it in the sep = argument of read.table!

Now, let’s read in the file combined_tidy_vcf.csv which will be located in /home/dcuser/r_data/. Call this data variants. The first argument to pass to our read.csv() function is the file path for our data. The file path must be in quotes and now is a good time to remember to use tab autocompletion. If you use tab autocompletion you avoid typos and errors in file paths. Use it!

### R

## read in a CSV file and save it as 'variants'

One of the first things you should notice is that in the Environment window, you have the variants object, listed as 801 obs. (observations/rows) of 29 variables (columns). Double-clicking on the name of the object will open a view of the data in a new tab.

## Summarizing, subsetting, and determining the structure of a data frame.

A data frame is the standard way in R to store tabular data. A data fame could also be thought of as a collection of vectors, all of which have the same length. Using only two functions, we can learn a lot about out data frame including some summary statistics as well as well as the “structure” of the data frame. Let’s examine what each of these functions can tell us:

### R

## get summary statistics on a data frame

summary(variants)

### OUTPUT

sample_id            CHROM                POS             ID
Length:801         Length:801         Min.   :   1521   Mode:logical
Class :character   Class :character   1st Qu.:1115970   NA's:801
Mode  :character   Mode  :character   Median :2290361
Mean   :2243682
3rd Qu.:3317082
Max.   :4629225

REF                ALT                 QUAL          FILTER
Length:801         Length:801         Min.   :  4.385   Mode:logical
Class :character   Class :character   1st Qu.:139.000   NA's:801
Mode  :character   Mode  :character   Median :195.000
Mean   :172.276
3rd Qu.:225.000
Max.   :228.000

INDEL              IDV              IMF               DP
Mode :logical   Min.   : 2.000   Min.   :0.5714   Min.   : 2.00
FALSE:700       1st Qu.: 7.000   1st Qu.:0.8824   1st Qu.: 7.00
TRUE :101       Median : 9.000   Median :1.0000   Median :10.00
Mean   : 9.396   Mean   :0.9219   Mean   :10.57
3rd Qu.:11.000   3rd Qu.:1.0000   3rd Qu.:13.00
Max.   :20.000   Max.   :1.0000   Max.   :79.00
NA's   :700      NA's   :700
VDB                 RPB              MQB              BQB
Min.   :0.0005387   Min.   :0.0000   Min.   :0.0000   Min.   :0.1153
1st Qu.:0.2180410   1st Qu.:0.3776   1st Qu.:0.1070   1st Qu.:0.6963
Median :0.4827410   Median :0.8663   Median :0.2872   Median :0.8615
Mean   :0.4926291   Mean   :0.6970   Mean   :0.5330   Mean   :0.7784
3rd Qu.:0.7598940   3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:1.0000
Max.   :0.9997130   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000
NA's   :773      NA's   :773      NA's   :773
MQSB              SGB               MQ0F           ICB
Min.   :0.01348   Min.   :-0.6931   Min.   :0.00000   Mode:logical
1st Qu.:0.95494   1st Qu.:-0.6762   1st Qu.:0.00000   NA's:801
Median :1.00000   Median :-0.6620   Median :0.00000
Mean   :0.96428   Mean   :-0.6444   Mean   :0.01127
3rd Qu.:1.00000   3rd Qu.:-0.6364   3rd Qu.:0.00000
Max.   :1.01283   Max.   :-0.4536   Max.   :0.66667
NA's   :48
HOB                AC          AN        DP4                  MQ
Mode:logical   Min.   :1   Min.   :1   Length:801         Min.   :10.00
NA's:801       1st Qu.:1   1st Qu.:1   Class :character   1st Qu.:60.00
Median :1   Median :1   Mode  :character   Median :60.00
Mean   :1   Mean   :1                      Mean   :58.19
3rd Qu.:1   3rd Qu.:1                      3rd Qu.:60.00
Max.   :1   Max.   :1                      Max.   :60.00

Indiv              gt_PL               gt_GT   gt_GT_alleles
Length:801         Length:801         Min.   :1   Length:801
Class :character   Class :character   1st Qu.:1   Class :character
Mode  :character   Mode  :character   Median :1   Mode  :character
Mean   :1
3rd Qu.:1
Max.   :1

Our data frame had 29 variables, so we get 29 fields that summarize the data. The QUAL, IMF, and VDB variables (and several others) are numerical data and so you get summary statistics on the min and max values for these columns, as well as mean, median, and interquartile ranges. Many of the other variables (e.g. sample_id) are treated as characters data (more on this in a bit).

There is a lot to work with, so we will subset the first three columns into a new data frame using the data.frame() function.

### R

## put the first three columns of variants into a new data frame called subset

subset <- data.frame(variants[, c(1:3, 6)])

Now, let’s use the str() (structure) function to look a little more closely at how data frames work:

### R

## get the structure of a data frame

str(subset)

### OUTPUT

'data.frame':	801 obs. of  4 variables:
$sample_id: chr "SRR2584863" "SRR2584863" "SRR2584863" "SRR2584863" ...$ CHROM    : chr  "CP000819.1" "CP000819.1" "CP000819.1" "CP000819.1" ...
$POS : int 9972 263235 281923 433359 473901 648692 1331794 1733343 2103887 2333538 ...$ ALT      : chr  "G" "T" "T" "CTTTTTTTT" ...

Ok, thats a lot up unpack! Some things to notice.

• the object type data.frame is displayed in the first row along with its dimensions, in this case 801 observations (rows) and 4 variables (columns)

### R

## extract the "ALT" column to a new object

### OUTPUT

[1] "SRR2584863" "SRR2584863" "SRR2584863" "SRR2584863" "SRR2584863"
[6] "SRR2584863"

variants[variantsREF == "A", ] ### OUTPUT sample_id CHROM POS ID REF ALT QUAL FILTER INDEL IDV IMF DP 11 SRR2584863 CP000819.1 2407766 NA A C 104 NA FALSE NA NA 9 12 SRR2584863 CP000819.1 2446984 NA A C 225 NA FALSE NA NA 20 14 SRR2584863 CP000819.1 2665639 NA A T 225 NA FALSE NA NA 19 16 SRR2584863 CP000819.1 3339313 NA A C 211 NA FALSE NA NA 10 18 SRR2584863 CP000819.1 3481820 NA A G 200 NA FALSE NA NA 9 19 SRR2584863 CP000819.1 3488669 NA A C 225 NA FALSE NA NA 13 VDB RPB MQB BQB MQSB SGB MQ0F ICB HOB AC 11 0.0230738 0.900802 0.150134 0.750668 0.500000 -0.590765 0.333333 NA NA 1 12 0.0714027 NA NA NA 1.000000 -0.689466 0.000000 NA NA 1 14 0.9960390 NA NA NA 1.000000 -0.690438 0.000000 NA NA 1 16 0.4059360 NA NA NA 1.007750 -0.670168 0.000000 NA NA 1 18 0.1070810 NA NA NA 0.974597 -0.662043 0.000000 NA NA 1 19 0.0162706 NA NA NA 1.000000 -0.680642 0.000000 NA NA 1 AN DP4 MQ 11 1 3,0,3,2 25 12 1 0,0,10,6 60 14 1 0,0,12,5 60 16 1 0,0,4,6 60 18 1 0,0,4,5 60 19 1 0,0,8,4 60 Indiv gt_PL 11 /home/dcuser/dc_workshop/results/bam/SRR2584863.aligned.sorted.bam 131,0 12 /home/dcuser/dc_workshop/results/bam/SRR2584863.aligned.sorted.bam 255,0 14 /home/dcuser/dc_workshop/results/bam/SRR2584863.aligned.sorted.bam 255,0 16 /home/dcuser/dc_workshop/results/bam/SRR2584863.aligned.sorted.bam 241,0 18 /home/dcuser/dc_workshop/results/bam/SRR2584863.aligned.sorted.bam 230,0 19 /home/dcuser/dc_workshop/results/bam/SRR2584863.aligned.sorted.bam 255,0 gt_GT gt_GT_alleles 11 1 C 12 1 C 14 1 T 16 1 C 18 1 G 19 1 C The subsetting notation is very similar to what we learned for vectors. The key differences include: • Typically provide two values separated by commas: data.frame[row, column] • In cases where you are taking a continuous range of numbers use a colon between the numbers (start:stop, inclusive) • For a non continuous set of numbers, pass a vector using c() • Index using the name of a column(s) by passing them as vectors using c() Finally, in all of the subsetting exercises above, we printed values to the screen. You can create a new data frame object by assigning them to a new object name: ### R # create a new data frame containing only observations from SRR2584863 SRR2584863_variants <- variants[variantssample_id == "SRR2584863", ]

# check the dimension of the data frame

dim(SRR2584863_variants)

[1] 25 29

### R

# get a summary of the data frame

summary(SRR2584863_variants)

### OUTPUT

sample_id            CHROM                POS             ID
Length:25          Length:25          Min.   :   9972   Mode:logical
Class :character   Class :character   1st Qu.:1331794   NA's:25
Mode  :character   Mode  :character   Median :2618472
Mean   :2464989
3rd Qu.:3488669
Max.   :4616538

REF                ALT                 QUAL         FILTER
Length:25          Length:25          Min.   : 31.89   Mode:logical
Class :character   Class :character   1st Qu.:104.00   NA's:25
Mode  :character   Mode  :character   Median :211.00
Mean   :172.97
3rd Qu.:225.00
Max.   :228.00

INDEL              IDV             IMF               DP
Mode :logical   Min.   : 2.00   Min.   :0.6667   Min.   : 2.0
FALSE:19        1st Qu.: 3.25   1st Qu.:0.9250   1st Qu.: 9.0
TRUE :6         Median : 8.00   Median :1.0000   Median :10.0
Mean   : 7.00   Mean   :0.9278   Mean   :10.4
3rd Qu.: 9.75   3rd Qu.:1.0000   3rd Qu.:12.0
Max.   :12.00   Max.   :1.0000   Max.   :20.0
NA's   :19      NA's   :19
VDB               RPB              MQB               BQB
Min.   :0.01627   Min.   :0.9008   Min.   :0.04979   Min.   :0.7507
1st Qu.:0.07140   1st Qu.:0.9275   1st Qu.:0.09996   1st Qu.:0.7627
Median :0.37674   Median :0.9542   Median :0.15013   Median :0.7748
Mean   :0.40429   Mean   :0.9517   Mean   :0.39997   Mean   :0.8418
3rd Qu.:0.65951   3rd Qu.:0.9771   3rd Qu.:0.57507   3rd Qu.:0.8874
Max.   :0.99604   Max.   :1.0000   Max.   :1.00000   Max.   :1.0000
NA's   :22       NA's   :22        NA's   :22
MQSB             SGB               MQ0F           ICB
Min.   :0.5000   Min.   :-0.6904   Min.   :0.00000   Mode:logical
1st Qu.:0.9599   1st Qu.:-0.6762   1st Qu.:0.00000   NA's:25
Median :0.9962   Median :-0.6620   Median :0.00000
Mean   :0.9442   Mean   :-0.6341   Mean   :0.04667
3rd Qu.:1.0000   3rd Qu.:-0.6168   3rd Qu.:0.00000
Max.   :1.0128   Max.   :-0.4536   Max.   :0.66667
NA's   :3
HOB                AC          AN        DP4                  MQ
Mode:logical   Min.   :1   Min.   :1   Length:25          Min.   :10.00
NA's:25        1st Qu.:1   1st Qu.:1   Class :character   1st Qu.:60.00
Median :1   Median :1   Mode  :character   Median :60.00
Mean   :1   Mean   :1                      Mean   :55.52
3rd Qu.:1   3rd Qu.:1                      3rd Qu.:60.00
Max.   :1   Max.   :1                      Max.   :60.00

Indiv              gt_PL               gt_GT   gt_GT_alleles
Length:25          Length:25          Min.   :1   Length:25
Class :character   Class :character   1st Qu.:1   Class :character
Mode  :character   Mode  :character   Median :1   Mode  :character
Mean   :1
3rd Qu.:1
Max.   :1

## Coercing values in data frames

### Tip: coercion isn’t limited to data frames

While we are going to address coercion in the context of data frames most of these methods apply to other data structures, such as vectors

Sometimes, it is possible that R will misinterpret the type of data represented in a data frame, or store that data in a mode which prevents you from operating on the data the way you wish. For example, a long list of gene names isn’t usually thought of as a categorical variable, the way that your experimental condition (e.g. control, treatment) might be. More importantly, some R packages you use to analyze your data may expect characters as input, not factors. At other times (such as plotting or some statistical analyses) a factor may be more appropriate. Ultimately, you should know how to change the mode of an object.

First, its very important to recognize that coercion happens in R all the time. This can be a good thing when R gets it right, or a bad thing when the result is not what you expect. Consider:

### R

snp_chromosomes <- c('3', '11', 'X', '6')
typeof(snp_chromosomes)

### OUTPUT

[1] "character"

Although there are several numbers in our vector, they are all in quotes, so we have explicitly told R to consider them as characters. However, even if we removed the quotes from the numbers, R would coerce everything into a character:

### R

snp_chromosomes_2 <- c(3, 11, 'X', 6)
typeof(snp_chromosomes_2)

[1] "character"

### R

snp_chromosomes_2[1]

### OUTPUT

[1] "3"

We can use the as. functions to explicitly coerce values from one form into another. Consider the following vector of characters, which all happen to be valid numbers:

### R

snp_positions_2 <- c("8762685", "66560624", "67545785", "154039662")
typeof(snp_positions_2)

[1] "character"

### R

snp_positions_2[1]

### OUTPUT

[1] "8762685"

Now we can coerce snp_positions_2 into a numeric type using as.numeric():

### R

snp_positions_2 <- as.numeric(snp_positions_2)
typeof(snp_positions_2)

[1] "double"

### R

snp_positions_2[1]

### OUTPUT

[1] 8762685

Sometimes coercion is straight forward, but what would happen if we tried using as.numeric() on snp_chromosomes_2

### R

snp_chromosomes_2 <- as.numeric(snp_chromosomes_2)

### WARNING

Warning: NAs introduced by coercion

If we check, we will see that an NA value (R’s default value for missing data) has been introduced.

### R

snp_chromosomes_2

### OUTPUT

[1]  3 11 NA  6

Trouble can really start when we try to coerce a factor. For example, when we try to coerce the factor_snps vector into a numeric mode look at the result:

### R

as.numeric(factor_snps)

### OUTPUT

[1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[38] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[75] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[112] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[149] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[186] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 4 4 4 4 4 4 4 4 4 4
[223] 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
[260] 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
[297] 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
[334] 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
[371] 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
[408] 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[445] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[482] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[519] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[556] 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[593] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[630] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[667] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[704] 2 2 2 2

Strangely, it works! Almost. Instead of giving an error message, R returns numeric values, which in this case are the integers assigned to the levels in this factor. This kind of behavior can lead to hard-to-find bugs, for example when we do have numbers in a factor, and we get numbers from a coercion. If we don’t look carefully, we may not notice a problem.

If you need to coerce an entire column you can overwrite it using an expression like this one:

### R

# make the 'REF' column a character type column

variants$REF <- as.character(variants$REF)

# check the type of the column

### OUTPUT

[1] 79

You can sort a data frame using the order() function:

### R

sorted_by_DP <- variants[order(variants$DP), ] head(sorted_by_DP$DP)

[1] 2 2 2 2 2 2

### Exercise

The order() function lists values in increasing order by default. Look at the documentation for this function and change sorted_by_DP to start with variants with the greatest filtered depth (“DP”).

### R

sorted_by_DP <- variants[order(variants$DP, decreasing = TRUE), ] head(sorted_by_DP$DP)

### OUTPUT

[1] 79 46 41 29 29 27

You can rename columns:

### R

colnames(variants)[colnames(variants) == "sample_id"] <- "strain"

# check the column name (hint names are returned as a vector)
colnames(variants)

### OUTPUT

[1] "strain"        "CHROM"         "POS"           "ID"
[5] "REF"           "ALT"           "QUAL"          "FILTER"
[9] "INDEL"         "IDV"           "IMF"           "DP"
[13] "VDB"           "RPB"           "MQB"           "BQB"
[17] "MQSB"          "SGB"           "MQ0F"          "ICB"
[21] "HOB"           "AC"            "AN"            "DP4"
[25] "MQ"            "Indiv"         "gt_PL"         "gt_GT"
[29] "gt_GT_alleles"

## Saving your data frame to a file

We can save data to a file. We will save our SRR2584863_variants object to a .csv file using the write.csv() function:

### R

write.csv(SRR2584863_variants, file = "data/SRR2584863_variants.csv")

The write.csv() function has some additional arguments listed in the help, but at a minimum you need to tell it what data frame to write to file, and give a path to a file name in quotes (if you only provide a file name, the file will be written in the current working directory).

## Importing data from Excel

Excel is one of the most common formats, so we need to discuss how to make these files play nicely with R. The simplest way to import data from Excel is to save your Excel file in .csv format*. You can then import into R right away. Sometimes you may not be able to do this (imagine you have data in 300 Excel files, are you going to open and export all of them?).

One common R package (a set of code with features you can download and add to your R installation) is the readxl package which can open and import Excel files. Rather than addressing package installation this second (we’ll discuss this soon!), we can take advantage of RStudio’s import feature which integrates this package. (Note: this feature is available only in the latest versions of RStudio such as is installed on our cloud instance).

First, in the RStudio menu go to File, select Import Dataset, and choose From Excel… (notice there are several other options you can explore).

Next, under File/Url: click the Browse button and navigate to the Ecoli_metadata.xlsx file located at /home/dcuser/dc_sample_data/R. You should now see a preview of the data to be imported:

Notice that you have the option to change the data type of each variable by clicking arrow (drop-down menu) next to each column title. Under Import Options you may also rename the data, choose a different sheet to import, and choose how you will handle headers and skipped rows. Under Code Preview you can see the code that will be used to import this file. We could have written this code and imported the Excel file without the RStudio import function, but now you can choose your preference.

In this exercise, we will leave the title of the data frame as Ecoli_metadata, and there are no other options we need to adjust. Click the Import button to import the data.

Finally, let’s check the first few lines of the Ecoli_metadata data frame:

### OUTPUT

# A tibble: 6 × 7
sample   generation clade   strain cit     run       genome_size
<chr>         <dbl> <chr>   <chr>  <chr>   <chr>           <dbl>
1 REL606            0 NA      REL606 unknown <NA>             4.62
2 REL1166A       2000 unknown REL606 unknown SRR098028        4.63
3 ZDB409         5000 unknown REL606 unknown SRR098281        4.6
4 ZDB429        10000 UC      REL606 unknown SRR098282        4.59
5 ZDB446        15000 UC      REL606 unknown SRR098283        4.66
6 ZDB458        20000 (C1,C2) REL606 unknown SRR098284        4.63

The type of this object is ‘tibble’, a type of data frame we will talk more about in the ‘dplyr’ section. If you needed a true R data frame you could coerce with as.data.frame().

### Exercise: Putting it all together - data frames

1. What are the dimensions (# rows, # columns) of the data frame?

2. What are categories are there in the cit column? hint: treat column as factor

3. How many of each of the cit categories are there?

4. What is the genome size for the 7th observation in this data set?

5. What is the median value of the variable genome_size

6. Rename the column sample to sample_id

7. Create a new column (name genome_size_bp) and set it equal to the genome_size multiplied by 1,000,000

8. Save the edited Ecoli_metadata data frame as “exercise_solution.csv” in your current working directory.

[1] 30  7

### OUTPUT

minus    plus unknown
9       9      12

### OUTPUT

# A tibble: 1 × 1
genome_size
<dbl>
1        4.62