Data preparation

A very large part of research work can consist of data gathering, cleaning, combining and formatting. You may spend much more time on data preparation than on analysis, modeling or visualization. R provides some tools to make this process easier; but doing this well also requires that you carefully consider your options. See Wickham (2014) for a discussion. Two very important functions in this context are reshape and merge. Sorting of data can also be helpful.

reshape

reshape allows you to rearrange data from a wide to a long form and vice versa. This can be a very important step to simplify data analysis. In the wide form, each variable is a column and each case (e.g. individual) is a singe row. This is the common ‘spreadsheet’ approach. In the long form there is column that indicates the variable name and a column that has its value. Other columns identify the cases, and these can be repeated many times. The long form can be much easier for use in data analysis than the wide form. In many cases you may want to go from one form to the other and back.

The function to go between wide and long form is called reshape. Unfortunately reshape is rather difficult to use. There is only one function to go from wide to long and vice versa, creating confusion about what arguments to use when. It is also poorly documented, and its error messages are bewildering. (There is a package called reshape that you can use instead. But in this Introduction I want to stick with base R idiom.)

There is a good example of how to use reshape here. And I will re-use that and expand on it.

wide to long

Get some example data on student’s performance in different subjects. Note how you can read a text on a web server (http address).

dd <- read.csv('https://stats.idre.ucla.edu/stat/data/hsb2.csv')
d <- dd[1:3, c(1:2, 8:9)]
d
##    id female write math
## 1  70      0    52   41
## 2 121      1    59   53
## 3  86      0    33   54

This is a “wide” form dataset. To go to a long form, you need to provide an argument varying that indicates the variables that are combined into one new variable. v.names is the name of this new variable.

wvars <- c("write", "math")
x <- reshape(d, varying=wvars, v.names="score", direction = "long")
x
##        id female time score
## 70.1   70      0    1    52
## 121.1 121      1    1    59
## 86.1   86      0    1    33
## 70.2   70      0    2    41
## 121.2 121      1    2    53
## 86.2   86      0    2    54

x has two new variables “time”, and “score”. As you can see, “score” has the values for “write” and “math” in the d. The “time” variable identifies which is which. “write” is identified with a 1, and “math” is identified with a 2. To have clear labels rather than such numbers, you can use the times argument; and add the timevar argument to rename “time” to something more meaningful. Note that the argument names are based on the idea that the data refer to different measurements over time. But this is not always the case.

x <- reshape(d, varying=wvars, v.names="score", times=wvars, timevar = "subject", direction = "long")
x
##            id female subject score
## 70.write   70      0   write    52
## 121.write 121      1   write    59
## 86.write   86      0   write    33
## 70.math    70      0    math    41
## 121.math  121      1    math    53
## 86.math    86      0    math    54

The row names also identify how the records were created. To get rid of the row names do

rownames(x) <- NULL
x
##    id female subject score
## 1  70      0   write    52
## 2 121      1   write    59
## 3  86      0   write    33
## 4  70      0    math    41
## 5 121      1    math    53
## 6  86      0    math    54

Variables “id” and “female” were unchanging, but they were duplicated because two variables (“write” and “math”) were combined into one (“subject”)

long to wide

To go from long to wide we need to use “idvar” and “timevar”. “idvar” identifies the variables that identify a single case (e.g. a single person, or other observational unit). In contrast, “timevar” indicates the variable that has the identifiers that become variables in the wide format. The remaining variable should have the values that match these new variables.

w <- reshape(x, idvar=c("id", "female"), timevar = "subject", direction = "wide")
w
##    id female score.write score.math
## 1  70      0          52         41
## 2 121      1          59         53
## 3  86      0          33         54

Note that w is identical to d, except for the last two column names that now have “score.” prepended to them. We can change that like this:

cn <- colnames(w)
cn <- gsub("score.", "", cn)
colnames(w) <- cn
w
##    id female write math
## 1  70      0    52   41
## 2 121      1    59   53
## 3  86      0    33   54

merge

A common situation is to have multiple data.frames with data for the same cases (e.g., individuals, fields, countries, …). Such data.frames may need to be joined such that they can be analyzed. For example here we have a and b:

a <- dd[, 1:3]
# random sample of 100 records)
set.seed(1)
b <- dd[sample(nrow(dd), 100), c(1, 7:10)]

a has 200 records. It has a unique identifier for each student and information about their sex (female or not) and race (4 groups).

dim(a)
## [1] 200   3
head(a)
##    id female race
## 1  70      0    4
## 2 121      1    4
## 3  86      0    4
## 4 141      0    4
## 5 172      0    4
## 6 113      0    4
table(a$female)
##
##   0   1
##  91 109
table(a$race)
##
##   1   2   3   4
##  24  11  20 145

b has the same unique identifier as a (but only for 100 students) and it has the grades for four subjects.

dim(b)
## [1] 100   5
head(b)
##      id read write math science
## 68  107   47    39   47      42
## 167 156   50    59   53      61
## 129  91   50    49   56      47
## 162  74   57    50   50      51
## 43  168   52    54   57      55
## 14  104   54    63   57      55

Imagine we were interested in differences in reading by sex or race. We would need to combine a and b. That, fortunately, is very simple, because we have the merge function.

ab <- merge(a, b, by='id')
head(ab)
##   id female race read write math science
## 1  3      0    1   63    65   48      63
## 2  6      1    1   47    41   46      40
## 3  8      1    1   39    44   52      44
## 4  9      0    1   48    49   52      44
## 5 12      0    1   37    44   45      39
## 6 13      1    1   47    46   39      47

Always check the dimensions of the result

dim(ab)
## [1] 100   7

In this case we expected 100 records (the lower number of the two; a had 200 records, but b only 100). Sometimes you get fewer than expected, suggesting that the identifiers do not match. In other cases you might want to keep all records, and create missing values where these are not available. You can do that like this:

ab <- merge(a, b, by='id', all.x=TRUE)
dim(ab)
## [1] 200   7
head(ab)
##   id female race read write math science
## 1  1      1    1   NA    NA   NA      NA
## 2  2      1    1   NA    NA   NA      NA
## 3  3      0    1   63    65   48      63
## 4  4      1    1   NA    NA   NA      NA
## 5  5      0    1   NA    NA   NA      NA
## 6  6      1    1   47    41   46      40

Note that the “x” in all.x refers to the first argument, hence a in this case. In other cases you might need to say all.y=TRUE or all=TRUE. Consider these extreme cases (with no matching records):

merge(a[1:3,], b[1:3, ], by='id')
## [1] id      female  race    read    write   math    science
## <0 rows> (or 0-length row.names)
merge(a[1:3,], b[1:3, ], by='id', all.x=T)
##    id female race read write math science
## 1  70      0    4   NA    NA   NA      NA
## 2  86      0    4   NA    NA   NA      NA
## 3 121      1    4   NA    NA   NA      NA
merge(a[1:3,], b[1:3, ], by='id', all.y=T)
##    id female race read write math science
## 1  91     NA   NA   50    49   56      47
## 2 107     NA   NA   47    39   47      42
## 3 156     NA   NA   50    59   53      61
merge(a[1:3,], b[1:3, ], by='id', all=T)
##    id female race read write math science
## 1  70      0    4   NA    NA   NA      NA
## 2  86      0    4   NA    NA   NA      NA
## 3  91     NA   NA   50    49   56      47
## 4 107     NA   NA   47    39   47      42
## 5 121      1    4   NA    NA   NA      NA
## 6 156     NA   NA   50    59   53      61

Now that we have ab we can compute what we needed:

tapply(ab$read, ab$female, mean, na.rm=TRUE)
##        0        1
## 53.43902 52.20339
tapply(ab$read, ab$race, mean, na.rm=TRUE)
##        1        2        3        4
## 49.69231 56.00000 45.00000 54.22535

sort

It is often useful to sort data to make it easier to inspect it. R has a sort function but that is only for vectors. For matrices or data.frames you need to use the order function.

sort is straightforward:

x <- sample(10)
x
##  [1] 10  8  2  5  4  7  6  9  3  1
sort(x)
##  [1]  1  2  3  4  5  6  7  8  9 10

Now consider order:

i <- order(x)
i
##  [1] 10  3  9  5  4  7  6  2  8  1
x[i]
##  [1]  1  2  3  4  5  6  7  8  9 10

order returns a vector that allows you to sort. The first value of i is 7. This means that x[7] should be the lowest number in x. The next number is x[6] followed by x[3] and x[2] and so forth.

Consider data.frame x:

set.seed(0)
x <- a[sample(nrow(a), 10), ]
x
##      id female race
## 142  90      1    4
## 68  107      0    4
## 167 156      1    4
## 129  91      1    4
## 162  74      1    4
## 43  168      0    4
## 14  104      0    4
## 187 158      1    4
## 51   27      0    2
## 85   58      0    4

Here is how you can use order to sort it by one column (“id” in this case):

oid <- order(x$id)
y <- x[oid, ]
y
##      id female race
## 51   27      0    2
## 85   58      0    4
## 162  74      1    4
## 142  90      1    4
## 129  91      1    4
## 14  104      0    4
## 68  107      0    4
## 167 156      1    4
## 187 158      1    4
## 43  168      0    4

Or by multiple columns. In this case, we want to sort first by “race”, then by “female” and then by “id”:

oid <- order(x$race, x$female, x$id)
x[oid, ]
##      id female race
## 51   27      0    2
## 85   58      0    4
## 14  104      0    4
## 68  107      0    4
## 43  168      0    4
## 162  74      1    4
## 142  90      1    4
## 129  91      1    4
## 167 156      1    4
## 187 158      1    4