Part 1: Starting with Data
Overview
Teaching: 40 min
Exercises: 20 minQuestions
What is a data.frame?
How can I read a complete csv file into R?
How can I get basic summary information about my dataset?
How can I change the way R treats strings in my dataset?
Why would I want strings to be treated differently?
How are dates represented in R and how can I change the format?
Objectives
Describe what a data frame is.
Load external data from a .csv file into a data frame.
Summarize the contents of a data frame.
Describe the difference between a factor and a string.
Convert between strings and factors.
Reorder and rename factors.
Change how character strings are handled in a data frame.
Examine and change date formats.
Presentation of the test data
The dataset we will be working with for this lesson has scores for items from a listening and reading placement test.
column_name | description |
---|---|
ID | A unique ID for each test taker |
names | Fake names of the test takers |
origin | Fake areas of origin of the test takers |
q*_list_mi | A main idea question on the listening test |
q*_list_det | A detail question on the listening test |
q*_list_inf | An inference question on the listening test |
q*_list_prag | A pragmatics question on the listening test |
q*_read_mi | A main idea question about on the reading test |
q*_read_voc | A vocabulary question about on the reading test |
q*_read_det | A detail question about on the reading test |
q*_read_trog | A text organization/discourse question about on the reading test |
q*_read_inf | An inference question about on the reading test |
q*_read_purp | A question about the author’s purpose on the reading test |
q*_an | An anchor question |
You are going load the data in R’s memory using the function read_csv()
from the readr
package which is part of the tidyverse
. So, before we can use the read_csv()
function, we need to load the package. Also, if you recall, the missing data is encoded as “NULL” in the dataset. We’ll tell it to the function, so R will automatically convert all the “NULL” entries in the dataset into NA
.
library(tidyverse)
test_results_1 <- read_csv("data/placement_1.csv")
This statement doesn’t produce any output because, as you might recall,
assignments don’t display anything. If we want to check that our data has been
loaded, we can see the contents of the data frame by typing its name: test_results_1
.
test_results_1
## Try also
## View(test_results_1)
## head(test_results_1)
# A tibble: 88 x 74
ID names country admin_date q1_list_mi q2_list_det
<dbl> <chr> <chr> <dttm> <dbl> <dbl>
1 635 JUDr… china 1960-12-25 22:02:34 1 1
2 636 pan … china 1985-10-07 21:18:00 0 1
3 637 Nadě… china 1987-12-22 17:04:22 1 0
4 638 Mári… china 1983-10-31 10:23:45 0 1
5 639 MUDr… china 1930-12-28 11:00:55 1 1
6 640 Anež… china 1978-08-16 01:04:50 1 0
7 641 Magd… china 2011-01-12 04:11:12 0 1
8 642 Alžb… china 1977-11-19 01:12:51 0 0
9 643 Lubo… china 2012-07-30 10:15:54 0 0
10 644 Sára… china 1963-05-19 22:58:51 0 1
# … with 78 more rows, and 68 more variables: q3_list_det <dbl>,
# q4_list_det <dbl>, q5_list_det <dbl>, q6_list_det <dbl>,
# q7_list_det <dbl>, q8_list_det <dbl>, q9_list_det <dbl>,
# q10_list_mi <dbl>, q11_list_det <dbl>, q12_list_mi <dbl>,
# q13_list_prag <dbl>, q14_list_det <dbl>, q15_list_det <dbl>,
# q16_list_det <dbl>, q17_list_inf <dbl>, q18_list_inf <dbl>,
# q19_list_det <dbl>, q20_list_mi <dbl>, q21_list_det <dbl>,
# q22_list_prag <dbl>, q23_list_mi <dbl>, q24_list_det <dbl>,
# q25_list_prag <dbl>, q26_list_det_an <dbl>, q27_list_det_an <dbl>,
# q28_list_det_an <dbl>, q29_list_prag_an <dbl>, q30_list_mi_an <dbl>,
# q31_list_mi_an <dbl>, q32_list_det_an <dbl>, q33_list_det <dbl>,
# q34_list_prag_an <dbl>, q35_list_inf_an <dbl>, q36_read_mi <dbl>,
# q37_read_det <dbl>, q38_read_det <dbl>, q39_read_mi <dbl>,
# q40_read_voc <dbl>, q41_read_voc <dbl>, q42_read_mi <dbl>,
# q43_read_det <dbl>, q44_read_mi <dbl>, q45_read_det <dbl>,
# q46_read_mi <dbl>, q47_read_torg <dbl>, q48_read_det <dbl>,
# q49_read_det <dbl>, q50_read_voc <dbl>, q51_read_torg <dbl>,
# q52_read_inf <dbl>, q53_read_torg <dbl>, q54_read_det <dbl>,
# q55_read_torg <dbl>, q56_read_purp <dbl>, q57_read_purp <dbl>,
# q58_read_mi <dbl>, q59_read_inf <dbl>, q60_read_det_an <dbl>,
# q61_read_purp_an <dbl>, q62_read_voc_an <dbl>, q63_read_inf_an <dbl>,
# q64_read_mi_an <dbl>, q65_read_torg_an <dbl>, q66_read_torg_an <dbl>,
# q67_read_purp_an <dbl>, q68_read_mi_an <dbl>, q69_read_det_an <dbl>,
# q70_read_det_an <dbl>
Note
read_csv()
assumes that fields are delineated by commas, however, in several countries, the comma is used as a decimal separator and the semicolon (;) is used as a field delineator. If you want to read in this type of files in R, you can use theread_csv2
function. It behaves exactly likeread_csv
but uses different parameters for the decimal and the field separators. If you are working with another format, they can be both specified by the user. Check out the help forread_csv()
by typing?read_csv
to learn more. There is also theread_tsv()
for tab-separated data files, andread_delim()
allows you to specify more details about the structure of your file.
What are data frames and tibbles?
Data frames are the de facto data structure for tabular data in R
, and what
we use for data processing, statistics, and plotting.
A data frame is the representation of data in the format of a table where the columns are vectors that all have the same length. Because columns are vectors, each column must contain a single type of data (e.g., characters, integers, factors). For example, here is a figure depicting a data frame comprising a numeric, a character, and a logical vector.
A data frame can be created by hand, but most commonly they are generated by the
functions read_csv()
or read_table()
; in other words, when importing
spreadsheets from your hard drive (or the web).
A tibble is an extension of R
data frames used by the tidyverse. When the
data is read using read_csv()
, it is stored in an object of class tbl_df
,
tbl
, and data.frame
. You can see the class of an object with
class(test_results_1)
[1] "spec_tbl_df" "tbl_df" "tbl" "data.frame"
As a tibble
, the type of data included in each column is listed in an
abbreviated fashion below the column names. For instance, here ID
is a
column of doubles (abbreviated <dbl>
), and names
is a column of characters
(<chr>
).
Inspecting data frames
When calling a tbl_df
object (like test_results_1
here), there is already a lot of information about our data frame being displayed such as the number of rows, the number of columns, the names of the columns, and as we just saw the class of data stored in each column. However, there are functions to extract this information from data frames. Here is a non-exhaustive list of some of these
functions. Let’s try them out!
- Size:
dim(test_results_1)
- returns a vector with the number of rows in the first element, and the number of columns as the second element (the dimensions of the object)nrow(test_results_1)
- returns the number of rows-
ncol(test_results_1)
- returns the number of columns - Content:
head(test_results_1)
- shows the first 6 rows-
tail(test_results_1)
- shows the last 6 rows - Names:
-
names(test_results_1)
- returns the column names (synonym ofcolnames()
fordata.frame
objects) - Summary:
str(test_results_1)
- structure of the object and information about the class, length and content of each columnsummary(test_results_1)
- summary statistics for each column
Note: most of these functions are “generic”, they can be used on other types of objects besides data frames.
Indexing and subsetting data frames
Our interviews data frame has rows and columns (it has 2 dimensions), if we want to extract some specific data from it, we need to specify the “coordinates” we want from it. Row numbers come first, followed by column numbers. However, note that different ways of specifying these coordinates lead to results with different classes.
## first element in the first column of the data frame (as a vector)
test_results_1[1, 1]
# A tibble: 1 x 1
ID
<dbl>
1 635
## first element in the 6th column (as a vector)
test_results_1[1, 6]
# A tibble: 1 x 1
q2_list_det
<dbl>
1 1
## first column of the data frame (as a vector)
test_results_1[, 1]
# A tibble: 88 x 1
ID
<dbl>
1 635
2 636
3 637
4 638
5 639
6 640
7 641
8 642
9 643
10 644
# … with 78 more rows
## first column of the data frame (as a data.frame)
test_results_1[1]
# A tibble: 88 x 1
ID
<dbl>
1 635
2 636
3 637
4 638
5 639
6 640
7 641
8 642
9 643
10 644
# … with 78 more rows
## first three elements in the 7th column (as a vector)
test_results_1[1:3, 7]
# A tibble: 3 x 1
q3_list_det
<dbl>
1 1
2 1
3 1
## the 3rd row of the data frame (as a data.frame)
test_results_1[3, ]
# A tibble: 1 x 74
ID names country admin_date q1_list_mi q2_list_det
<dbl> <chr> <chr> <dttm> <dbl> <dbl>
1 637 Nadě… china 1987-12-22 17:04:22 1 0
# … with 68 more variables: q3_list_det <dbl>, q4_list_det <dbl>,
# q5_list_det <dbl>, q6_list_det <dbl>, q7_list_det <dbl>,
# q8_list_det <dbl>, q9_list_det <dbl>, q10_list_mi <dbl>,
# q11_list_det <dbl>, q12_list_mi <dbl>, q13_list_prag <dbl>,
# q14_list_det <dbl>, q15_list_det <dbl>, q16_list_det <dbl>,
# q17_list_inf <dbl>, q18_list_inf <dbl>, q19_list_det <dbl>,
# q20_list_mi <dbl>, q21_list_det <dbl>, q22_list_prag <dbl>,
# q23_list_mi <dbl>, q24_list_det <dbl>, q25_list_prag <dbl>,
# q26_list_det_an <dbl>, q27_list_det_an <dbl>, q28_list_det_an <dbl>,
# q29_list_prag_an <dbl>, q30_list_mi_an <dbl>, q31_list_mi_an <dbl>,
# q32_list_det_an <dbl>, q33_list_det <dbl>, q34_list_prag_an <dbl>,
# q35_list_inf_an <dbl>, q36_read_mi <dbl>, q37_read_det <dbl>,
# q38_read_det <dbl>, q39_read_mi <dbl>, q40_read_voc <dbl>,
# q41_read_voc <dbl>, q42_read_mi <dbl>, q43_read_det <dbl>,
# q44_read_mi <dbl>, q45_read_det <dbl>, q46_read_mi <dbl>,
# q47_read_torg <dbl>, q48_read_det <dbl>, q49_read_det <dbl>,
# q50_read_voc <dbl>, q51_read_torg <dbl>, q52_read_inf <dbl>,
# q53_read_torg <dbl>, q54_read_det <dbl>, q55_read_torg <dbl>,
# q56_read_purp <dbl>, q57_read_purp <dbl>, q58_read_mi <dbl>,
# q59_read_inf <dbl>, q60_read_det_an <dbl>, q61_read_purp_an <dbl>,
# q62_read_voc_an <dbl>, q63_read_inf_an <dbl>, q64_read_mi_an <dbl>,
# q65_read_torg_an <dbl>, q66_read_torg_an <dbl>,
# q67_read_purp_an <dbl>, q68_read_mi_an <dbl>, q69_read_det_an <dbl>,
# q70_read_det_an <dbl>
## equivalent to head_test_results_1 <- head(test_results_1)
test_results_1 <- test_results_1[1:6, ]
:
is a special function that creates numeric vectors of integers in increasing
or decreasing order, test 1:10
and 10:1
for instance.
You can also exclude certain indices of a data frame using the “-
” sign:
test_results_1[, -1] # The whole data frame, except the first column
# A tibble: 6 x 73
names country admin_date q1_list_mi q2_list_det q3_list_det
<chr> <chr> <dttm> <dbl> <dbl> <dbl>
1 JUDr… china 1960-12-25 22:02:34 1 1 1
2 pan … china 1985-10-07 21:18:00 0 1 1
3 Nadě… china 1987-12-22 17:04:22 1 0 1
4 Mári… china 1983-10-31 10:23:45 0 1 1
5 MUDr… china 1930-12-28 11:00:55 1 1 0
6 Anež… china 1978-08-16 01:04:50 1 0 1
# … with 67 more variables: q4_list_det <dbl>, q5_list_det <dbl>,
# q6_list_det <dbl>, q7_list_det <dbl>, q8_list_det <dbl>,
# q9_list_det <dbl>, q10_list_mi <dbl>, q11_list_det <dbl>,
# q12_list_mi <dbl>, q13_list_prag <dbl>, q14_list_det <dbl>,
# q15_list_det <dbl>, q16_list_det <dbl>, q17_list_inf <dbl>,
# q18_list_inf <dbl>, q19_list_det <dbl>, q20_list_mi <dbl>,
# q21_list_det <dbl>, q22_list_prag <dbl>, q23_list_mi <dbl>,
# q24_list_det <dbl>, q25_list_prag <dbl>, q26_list_det_an <dbl>,
# q27_list_det_an <dbl>, q28_list_det_an <dbl>, q29_list_prag_an <dbl>,
# q30_list_mi_an <dbl>, q31_list_mi_an <dbl>, q32_list_det_an <dbl>,
# q33_list_det <dbl>, q34_list_prag_an <dbl>, q35_list_inf_an <dbl>,
# q36_read_mi <dbl>, q37_read_det <dbl>, q38_read_det <dbl>,
# q39_read_mi <dbl>, q40_read_voc <dbl>, q41_read_voc <dbl>,
# q42_read_mi <dbl>, q43_read_det <dbl>, q44_read_mi <dbl>,
# q45_read_det <dbl>, q46_read_mi <dbl>, q47_read_torg <dbl>,
# q48_read_det <dbl>, q49_read_det <dbl>, q50_read_voc <dbl>,
# q51_read_torg <dbl>, q52_read_inf <dbl>, q53_read_torg <dbl>,
# q54_read_det <dbl>, q55_read_torg <dbl>, q56_read_purp <dbl>,
# q57_read_purp <dbl>, q58_read_mi <dbl>, q59_read_inf <dbl>,
# q60_read_det_an <dbl>, q61_read_purp_an <dbl>, q62_read_voc_an <dbl>,
# q63_read_inf_an <dbl>, q64_read_mi_an <dbl>, q65_read_torg_an <dbl>,
# q66_read_torg_an <dbl>, q67_read_purp_an <dbl>, q68_read_mi_an <dbl>,
# q69_read_det_an <dbl>, q70_read_det_an <dbl>
test_results_1[-c(7:88), ] # Equivalent to head(test_results_1)
# A tibble: 6 x 74
ID names country admin_date q1_list_mi q2_list_det
<dbl> <chr> <chr> <dttm> <dbl> <dbl>
1 635 JUDr… china 1960-12-25 22:02:34 1 1
2 636 pan … china 1985-10-07 21:18:00 0 1
3 637 Nadě… china 1987-12-22 17:04:22 1 0
4 638 Mári… china 1983-10-31 10:23:45 0 1
5 639 MUDr… china 1930-12-28 11:00:55 1 1
6 640 Anež… china 1978-08-16 01:04:50 1 0
# … with 68 more variables: q3_list_det <dbl>, q4_list_det <dbl>,
# q5_list_det <dbl>, q6_list_det <dbl>, q7_list_det <dbl>,
# q8_list_det <dbl>, q9_list_det <dbl>, q10_list_mi <dbl>,
# q11_list_det <dbl>, q12_list_mi <dbl>, q13_list_prag <dbl>,
# q14_list_det <dbl>, q15_list_det <dbl>, q16_list_det <dbl>,
# q17_list_inf <dbl>, q18_list_inf <dbl>, q19_list_det <dbl>,
# q20_list_mi <dbl>, q21_list_det <dbl>, q22_list_prag <dbl>,
# q23_list_mi <dbl>, q24_list_det <dbl>, q25_list_prag <dbl>,
# q26_list_det_an <dbl>, q27_list_det_an <dbl>, q28_list_det_an <dbl>,
# q29_list_prag_an <dbl>, q30_list_mi_an <dbl>, q31_list_mi_an <dbl>,
# q32_list_det_an <dbl>, q33_list_det <dbl>, q34_list_prag_an <dbl>,
# q35_list_inf_an <dbl>, q36_read_mi <dbl>, q37_read_det <dbl>,
# q38_read_det <dbl>, q39_read_mi <dbl>, q40_read_voc <dbl>,
# q41_read_voc <dbl>, q42_read_mi <dbl>, q43_read_det <dbl>,
# q44_read_mi <dbl>, q45_read_det <dbl>, q46_read_mi <dbl>,
# q47_read_torg <dbl>, q48_read_det <dbl>, q49_read_det <dbl>,
# q50_read_voc <dbl>, q51_read_torg <dbl>, q52_read_inf <dbl>,
# q53_read_torg <dbl>, q54_read_det <dbl>, q55_read_torg <dbl>,
# q56_read_purp <dbl>, q57_read_purp <dbl>, q58_read_mi <dbl>,
# q59_read_inf <dbl>, q60_read_det_an <dbl>, q61_read_purp_an <dbl>,
# q62_read_voc_an <dbl>, q63_read_inf_an <dbl>, q64_read_mi_an <dbl>,
# q65_read_torg_an <dbl>, q66_read_torg_an <dbl>,
# q67_read_purp_an <dbl>, q68_read_mi_an <dbl>, q69_read_det_an <dbl>,
# q70_read_det_an <dbl>
Data frames can be subset by calling indices (as shown previously), but also by calling their column names directly:
test_results_1["country"] # Result is a data frame
test_results_1[, "country"] # Result is a data frame
test_results_1[["country"]] # Result is a vector
test_results_1$country # Result is a vector
In RStudio, you can use the autocompletion feature to get the full and correct names of the columns.
Exercise
Create a data frame (
test_results_1_50
) containing only the data in row 50 of thetest_results_1
dataset.Notice how
nrow()
gave you the number of rows in a data frame?
- Use that number to pull out just that last row in the data frame.
- Compare that with what you see as the last row using
tail()
to make sure it’s meeting expectations.- Pull out that last row using
nrow()
instead of the row number.- Create a new data frame (
test_results_1_last
) from that last row.Use
nrow()
to extract the row that is in the middle of the data frame. Store the content of this row in an object namedtest_results_1_middle
.Combine
nrow()
with the-
notation above to reproduce the behavior ofhead(test_results_1)
, keeping just the first through 6th rows of the test results dataset.Solution
## 1. test_results_1_50 <- test_results_1[50, ] ## 2. # Saving `n_rows` to improve readability and reduce duplication n_rows <- nrow(test_results_1) test_results_1_last <- test_results_1[n_rows, ] ## 3. test_results_1_middle <- test_results_1[n_rows / 2, ] ## 4. test_results_1_head <- test_results_1[-(7:n_rows), ]
Factors
R has a special data class, called factor, to deal with categorical data that you may encounter when creating plots or doing statistical analyses. Factors are very useful and actually contribute to making R particularly well suited to working with data. So we are going to spend a little time introducing them.
Factors represent categorical data. They are stored as integers associated with labels and they can be ordered or unordered. While factors look (and often behave) like character vectors, they are actually treated as integer vectors by R. So you need to be very careful when treating them as strings.
Once created, factors can only contain a pre-defined set of values, known as levels. By default, R always sorts levels in alphabetical order. For instance, if you have a factor with 2 levels:
country <- factor(c("Peru", "Netherlands", "Chile", "Kenya"))
R will assign 1
to the level "Chile"
and 4
to the level "Peru"
(because c
comes before p
, even though the first element in this vector is
"Peru"
). You can see this by using the function levels()
and you can find
the number of levels using nlevels()
:
levels(country)
[1] "Chile" "Kenya" "Netherlands" "Peru"
nlevels(country)
[1] 4
Sometimes, the order of the factors does not matter, other times you might want
to specify the order because it is meaningful (e.g., “low”, “medium”, “high”),
it improves your visualization, or it is required by a particular type of
analysis. Here, one way to reorder our levels in the country
vector would be:
country # current order
[1] Peru Netherlands Chile Kenya
Levels: Chile Kenya Netherlands Peru
country <- factor(country, levels = c("Peru", "Netherlands", "Chile", "Kenya"))
country # after re-ordering
[1] Peru Netherlands Chile Kenya
Levels: Peru Netherlands Chile Kenya
In R’s memory, these factors are represented by integers (1, 2), but are more
informative than integers because factors are self describing: "Chile"
,
"Kenya"
is more descriptive than 1
, and 2
. Which one is “Chile”? You
wouldn’t be able to tell just from the integer data. Factors, on the other hand,
have this information built in. It is particularly helpful when there are many
levels. It also makes renaming levels easier. Let’s say we made a mistake and need to recode “Chile” to “Argentina”.
levels(country)
[1] "Peru" "Netherlands" "Chile" "Kenya"
levels(country)[3] <- "Argentina"
levels(country)
[1] "Peru" "Netherlands" "Argentina" "Kenya"
country
[1] Peru Netherlands Argentina Kenya
Levels: Peru Netherlands Argentina Kenya
Converting factors
If you need to convert a factor to a character vector, you use
as.character(x)
.
as.character(country)
[1] "Peru" "Netherlands" "Argentina" "Kenya"
Converting factors where the levels appear as numbers (such as concentration
levels, or years) to a numeric vector is a little trickier. The as.numeric()
function returns the index values of the factor, not its levels, so it will
result in an entirely new (and unwanted in this case) set of numbers.
One method to avoid this is to convert factors to characters, and then to numbers.
Another method is to use the levels()
function. Compare:
year_fct <- factor(c(1990, 1983, 1977, 1998, 1990))
as.numeric(year_fct) # Wrong! And there is no warning...
[1] 3 2 1 4 3
as.numeric(as.character(year_fct)) # Works...
[1] 1990 1983 1977 1998 1990
as.numeric(levels(year_fct))[year_fct] # The recommended way.
[1] 1990 1983 1977 1998 1990
Notice that in the recommended levels()
approach, three important steps occur:
- We obtain all the factor levels using
levels(year_fct)
- We convert these levels to numeric values using
as.numeric(levels(year_fct))
- We then access these numeric values using the underlying integers of the
vector
year_fct
inside the square brackets
Renaming factors
When your data is stored as a factor, you can use the plot()
function to get a
quick glance at the number of observations represented by each factor level.
Let’s extract the country
column from our data frame, convert it into a
factor, and use it to look at the number of test takers from different
parts of the world:
## create a vector from the data frame column "country"
res_country <- test_results_1$country
## convert it into a factor
res_country <- as.factor(res_country)
## print the vector
res_country
## plot the vector
plot(res_country)
Exercise
Rename the levels of the factor to have the first letter in uppercase: “China”,”Morrocco”, and “Russia”.
Now that we have renamed the factor levels, can you recreate the barplot such that the labels are in reverse alphabet order?
Solution
## 1. levels(res_country) <- c("China", "Morocco", "Russia") ## or 2. res_country <- factor(res_country, levels = c("Russia", "Morocco", "China")) plot(res_country)
Key Points
Use read_csv to read tabular data in R.
Use factors to represent categorical data in R.