This book excerpt from “R for Everyone” details the methods in the R programming language to prepare your data for analysis Credit: Thinkstock Combining multiple datasets, whether by stacking or joining, is commonly necessary as is changing the shape of data. The plyr and reshape2 packages offer good functions for accomplishing this in addition to base tools such as rbind, cbind, and merge. Manipulating data takes a great deal of effort before serious analysis can begin. In this chapter, I consider when the data need to be rearranged from column-oriented to row-oriented (or the opposite) and when the data are in multiple, separate sets and need to be combined into one. There are base functions to accomplish these tasks, but I focus on those in plyr, reshape2, and data.table. While the tools covered in this chapter still form the backbone of data reshaping, newer packages like tidyr and dplyr are starting to supersede them. cbind and rbind The simplest case is when you have a two datasets with either identical columns (both the number of and names) or the same number of rows. In this case, either rbind or cbind work great. As a first trivial example, I create two simple data.frames by combining a few vectors with cbind, and then stack them using rbind. > # make three vectors and combine them as columns in a data.frame > sport <- c("Hockey", "Baseball", "Football") > league <- c("NHL", "MLB", "NFL") > trophy <- c("Stanley Cup", "Commissioner's Trophy", + "Vince Lombardi Trophy") > trophies1 <- cbind(sport, league, trophy) > > # make another data.frame using data.frame() > trophies2 <- data.frame(sport=c("Basketball", "Golf"), + league=c("NBA", "PGA"), + trophy=c("Larry O'Brien Championship Trophy", + "Wanamaker Trophy"), + stringsAsFactors=FALSE) > > # combine them into one data.frame with rbind > trophies <- rbind(trophies1, trophies2) Both cbind and rbind can take multiple arguments to combine an arbitrary number of objects. Note that it is possible to assign new column names to vectors in cbind. >> cbind(Sport=sport, Association=league, Prize=trophy) Sport Association Prize [1,] "Hockey" "NHL" "Stanley Cup" [2,] "Baseball" "MLB" "Commissioner's Trophy" [3,] "Football" "NFL" "Vince Lombardi Trophy" Joins Data do not always come so nicely aligned for combing using cbind and need to be joined together using a common key. This concept should be familiar to SQL users. Joins in R are not as flexible as SQL joins, but are still an essential operation in the data analysis process. The three most commonly used functions for joins are merge in base R, join in plyr, and the merging functionality in data.table. Each has pros and cons, with some pros outweighing their respective cons. Related video: How to create data visualizations in R To illustrate these functions I have prepared data originally made available as part of the USAID Open Government initiative. The data have been chopped into eight separate files so that they can be joined together. They are all available in a zip file at https://jaredlander.com/data/US_Foreign_Aid.zip. These should be downloaded and unzipped to a folder on our computer. This can be done a number of ways (including using a mouse!) but we show how to download and unzip using R.> download.file(url="http://jaredlander.com/data/US_Foreign_Aid.zip", + destfile="data/ForeignAid.zip") > unzip("data/ForeignAid.zip", exdir="data")To load all of these files programatically, we utilize a for loop as seen in Section 10.1. We get a list of the files using dir, and then loop through that list, assigning each dataset to a name specified using assign. The function str_sub extracts individual characters from a character vector and is explained in Section 16.3.>> library(stringr) > # first get a list of the files > theFiles <- dir("data/", pattern=".csv") > ## loop through those files > for(a in theFiles) + { + # build a good name to assign to the data + nameToUse <- str_sub(string=a, start=12, end=18) + # read in the csv using read.table + # file.path is a convenient way to specify a folder and file name + temp <- read.table(file=file.path("data", a), + header=TRUE, sep=",", stringsAsFactors=FALSE) + # assign them into the workspace + assign(x=nameToUse, value=temp) + }mergeR comes with a built-in function, called merge, to merge two data.frames. > Aid90s00s <- merge(x=Aid_90s, y=Aid_00s, + by.x=c("Country.Name", "Program.Name"), + by.y=c("Country.Name", "Program.Name")) > head(Aid90s00s) Country.Name Program.Name 1 Afghanistan Child Survival and Health 2 Afghanistan Department of Defense Security Assistance 3 Afghanistan Development Assistance 4 Afghanistan Economic Support Fund/Security Support Assistance 5 Afghanistan Food For Education 6 Afghanistan Global Health and Child Survival FY1990 FY1991 FY1992 FY1993 FY1994 FY1995 FY1996 FY1997 FY1998 1 NA NA NA NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA 4 NA NA NA 14178135 2769948 NA NA NA NA 5 NA NA NA NA NA NA NA NA NA 6 NA NA NA NA NA NA NA NA NA FY1999 FY2000 FY2001 FY2002 FY2003 FY2004 FY2005 1 NA NA NA 2586555 56501189 40215304 39817970 2 NA NA NA 2964313 NA 45635526 151334908 3 NA NA 4110478 8762080 54538965 180539337 193598227 4 NA NA 61144 31827014 341306822 1025522037 1157530168 5 NA NA NA NA 3957312 2610006 3254408 6 NA NA NA NA NA NA NA FY2006 FY2007 FY2008 FY2009 1 40856382 72527069 28397435 NA 2 230501318 214505892 495539084 552524990 3 212648440 173134034 150529862 3675202 4 1357750249 1266653993 1400237791 1418688520 5 386891 NA NA NA 6 NA NA 63064912 1764252The by.x specifies the key column(s) in the left data.frame and by.y does the same for the right data.frame. The ability to specify different column names for each data.frame is the most useful feature of merge. The biggest drawback, however, is that merge can be much slower than the alternatives.plyr joinReturning to Hadley Wickham’s plyr package, it includes a join function, which works similarly to merge but is much faster. The biggest drawback, though, is that the key column(s) in each table must have the same name. I use the same data used previously to illustrate.> library(plyr) > Aid90s00sJoin <- join(x=Aid_90s, y=Aid_00s, + by=c("Country.Name", "Program.Name")) > head(Aid90s00sJoin) Country.Name Program.Name 1 Afghanistan Child Survival and Health 2 Afghanistan Department of Defense Security Assistance 3 Afghanistan Development Assistance 4 Afghanistan Economic Support Fund/Security Support Assistance 5 Afghanistan Food For Education 6 Afghanistan Global Health and Child Survival FY1990 FY1991 FY1992 FY1993 FY1994 FY1995 FY1996 FY1997 FY1998 1 NA NA NA NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA 4 NA NA NA 14178135 2769948 NA NA NA NA 5 NA NA NA NA NA NA NA NA NA 6 NA NA NA NA NA NA NA NA NA FY1999 FY2000 FY2001 FY2002 FY2003 FY2004 FY2005 1 NA NA NA 2586555 56501189 40215304 39817970 2 NA NA NA 2964313 NA 45635526 151334908 3 NA NA 4110478 8762080 54538965 180539337 193598227 4 NA NA 61144 31827014 341306822 1025522037 1157530168 5 NA NA NA NA 3957312 2610006 3254408 6 NA NA NA NA NA NA NA FY2006 FY2007 FY2008 FY2009 1 40856382 72527069 28397435 NA 2 230501318 214505892 495539084 552524990 3 212648440 173134034 150529862 3675202 4 1357750249 1266653993 1400237791 1418688520 5 386891 NA NA NA 6 NA NA 63064912 1764252join has an argument for specifying a left, right, inner, or full (outer) join. If you have eight data.frames containing foreign assistance data that you would like to combine into one data.frame without hand coding each join, the best way to do this is put all the data.frames into a list, and then successively join them together using Reduce.> # first figure out the names of the data.frames > frameNames <- str_sub(string=theFiles, start=12, end=18) > # build an empty list > frameList <- vector("list", length(frameNames)) > names(frameList) <- frameNames > # add each data.frame into the list > for(a in frameNames) + { + frameList[[a]] <- eval(parse(text=a)) + }A lot happened in that section of code, so let’s go over it carefully. First, I reconstructed the names of the data.frames using str_sub from Hadley Wickham’s stringr package. Then I built an empty list with as many elements as there are data.frames—in this case eight—using vector and assigning its mode to “list.” I then set appropriate names to the list.Once the list was built and named, I looped through it, assigning to each element the appropriate data.frame. The problem is that I have the names of the data.frames as characters but the <- operator requires a variable, not a character. So I parsed and evaluated the character, which realizes the actual variable. Upon inspection, you can see that the list does indeed contain the appropriate data.frames. > head(frameList[[1]]) Country.Name Program.Name 1 Afghanistan Child Survival and Health 2 Afghanistan Department of Defense Security Assistance 3 Afghanistan Development Assistance 4 Afghanistan Economic Support Fund/Security Support Assistance 5 Afghanistan Food For Education 6 Afghanistan Global Health and Child Survival FY2000 FY2001 FY2002 FY2003 FY2004 FY2005 FY2006 1 NA NA 2586555 56501189 40215304 39817970 40856382 2 NA NA 2964313 NA 45635526 151334908 230501318 3 NA 4110478 8762080 54538965 180539337 193598227 212648440 4 NA 61144 31827014 341306822 1025522037 1157530168 1357750249 5 NA NA NA 3957312 2610006 3254408 386891 6 NA NA NA NA NA NA NA FY2007 FY2008 FY2009 1 72527069 28397435 NA 2 214505892 495539084 552524990 3 173134034 150529862 3675202 4 1266653993 1400237791 1418688520 5 NA NA NA 6 NA 63064912 1764252 > head(frameList[["Aid_00s"]]) Country.Name Program.Name 1 Afghanistan Child Survival and Health 2 Afghanistan Department of Defense Security Assistance 3 Afghanistan Development Assistance 4 Afghanistan Economic Support Fund/Security Support Assistance 5 Afghanistan Food For Education 6 Afghanistan Global Health and Child Survival FY2000 FY2001 FY2002 FY2003 FY2004 FY2005 FY2006 1 NA NA 2586555 56501189 40215304 39817970 40856382 2 NA NA 2964313 NA 45635526 151334908 230501318 3 NA 4110478 8762080 54538965 180539337 193598227 212648440 4 NA 61144 31827014 341306822 1025522037 1157530168 1357750249 5 NA NA NA 3957312 2610006 3254408 386891 6 NA NA NA NA NA NA NA FY2007 FY2008 FY2009 1 72527069 28397435 NA 2 214505892 495539084 552524990 3 173134034 150529862 3675202 4 1266653993 1400237791 1418688520 5 NA NA NA 6 NA 63064912 1764252 > head(frameList[[5]]) Country.Name Program.Name 1 Afghanistan Child Survival and Health 2 Afghanistan Department of Defense Security Assistance 3 Afghanistan Development Assistance 4 Afghanistan Economic Support Fund/Security Support Assistance 5 Afghanistan Food For Education 6 Afghanistan Global Health and Child Survival FY1960 FY1961 FY1962 FY1963 FY1964 FY1965 FY1966 FY1967 FY1968 1 NA NA NA NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA 4 NA NA 181177853 NA NA NA NA NA NA 5 NA NA NA NA NA NA NA NA NA 6 NA NA NA NA NA NA NA NA NA FY1969 1 NA 2 NA 3 NA 4 NA 5 NA 6 NA > head(frameList[["Aid_60s"]]) Country.Name Program.Name 1 Afghanistan Child Survival and Health 2 Afghanistan Department of Defense Security Assistance 3 Afghanistan Development Assistance 4 Afghanistan Economic Support Fund/Security Support Assistance 5 Afghanistan Food For Education 6 Afghanistan Global Health and Child Survival FY1960 FY1961 FY1962 FY1963 FY1964 FY1965 FY1966 FY1967 FY1968 1 NA NA NA NA NA NA NA NA NA 2 NA NA NA NA NA NA NA NA NA 3 NA NA NA NA NA NA NA NA NA 4 NA NA 181177853 NA NA NA NA NA NA 5 NA NA NA NA NA NA NA NA NA 6 NA NA NA NA NA NA NA NA NA FY1969 1 NA 2 NA 3 NA 4 NA 5 NA 6 NAHaving all the data.frames in a list lets you iterate through the list, joining all the elements together (or applying any function to the elements iteratively). Rather than using a loop, I used the Reduce function to speed the operation.> allAid <- Reduce(function(...){ + join(..., by=c("Country.Name", "Program.Name"))}, + frameList) > dim(allAid) [1] 2453 67 > library(useful) > corner(allAid, c=15) Country.Name Program.Name 1 Afghanistan Child Survival and Health 2 Afghanistan Department of Defense Security Assistance 3 Afghanistan Development Assistance 4 Afghanistan Economic Support Fund/Security Support Assistance 5 Afghanistan Food For Education FY2000 FY2001 FY2002 FY2003 FY2004 FY2005 FY2006 1 NA NA 2586555 56501189 40215304 39817970 40856382 2 NA NA 2964313 NA 45635526 151334908 230501318 3 NA 4110478 8762080 54538965 180539337 193598227 212648440 4 NA 61144 31827014 341306822 1025522037 1157530168 1357750249 5 NA NA NA 3957312 2610006 3254408 386891 FY2007 FY2008 FY2009 FY2010 FY1946 FY1947 1 72527069 28397435 NA NA NA NA 2 214505892 495539084 552524990 316514796 NA NA 3 173134034 150529862 3675202 NA NA NA 4 1266653993 1400237791 1418688520 2797488331 NA NA 5 NA NA NA NA NA NA > bottomleft(allAid, c=15) Country.Name Program.Name FY2000 FY2001 FY2002 2449 Zimbabwe Other State Assistance 1341952 322842 NA 2450 Zimbabwe Other USAID Assistance 3033599 8464897 6624408 2451 Zimbabwe Peace Corps 2140530 1150732 407834 2452 Zimbabwe Title I NA NA NA 2453 Zimbabwe Title II NA NA 31019776 FY2003 FY2004 FY2005 FY2006 FY2007 FY2008 FY2009 2449 NA 318655 44553 883546 1164632 2455592 2193057 2450 11580999 12805688 10091759 4567577 10627613 11466426 41940500 2451 NA NA NA NA NA NA NA 2452 NA NA NA NA NA NA NA 2453 NA NA NA 277468 100053600 180000717 174572685 FY2010 FY1946 FY1947 2449 1605765 NA NA 2450 30011970 NA NA 2451 NA NA NA 2452 NA NA NA 2453 79545100 NA NAReduce can be a difficult function to grasp, so let me illustrate it with a simple example. Let’s say you have a vector of the first ten integers, 1:10, and want to sum them (forget for a moment that sum(1:10) will work perfectly). You can call Reduce(sum, 1:10), which will first add 1 and 2. It will then add 3 to that result, then 4 to that result and so on, resulting in 55.Likewise, I passed a list to a function that joins its inputs, which in this case was simply ..., meaning that anything could be passed. (Using ... is an advanced trick of R programming that can be difficult to get right.) Reduce passed the first two data.frames in the list, which were then joined. That result was then joined to the next data.frame and so on until they were all joined together. data.table mergeLike many other operations in data.table, joining data requires a different syntax, and possibly a different way of thinking. To start, I convert two of my foreign aid datasets’ data.frames into data.tables.> library(data.table) > dt90 <- data.table(Aid_90s, key=c("Country.Name", "Program.Name")) > dt00 <- data.table(Aid_00s, key=c("Country.Name", "Program.Name"))Then, doing the join is a simple operation. Note that the join requires specifying the keys for the data.tables, which we did during their creation.> dt0090 <-dt90[dt00]In this case dt90 is the left side, dt00 is the right side, and a left join was performed. reshape2The next most common munging need is either melting data (going from column orientation to row orientation) or casting data (going from row orientation to column orientation). As with most other procedures in R, there are multiple functions available to accomplish these tasks, but we will focus on Hadley Wickham’s reshape2 package. We talk about Wickham a lot, but that is because his products have become so fundamental to the R developer’s toolbox.meltLooking at the Aid_00s data.frame, you can see that each year is stored in its own column. That is, the dollar amount for a given country and program is found in a different column for each year. This is called a cross table, which while nice for human consumption, is not ideal for graphing with ggplot2 or for some analysis algorithms.> head(Aid_00s) Country.Name Program.Name 1 Afghanistan Child Survival and Health 2 Afghanistan Department of Defense Security Assistance 3 Afghanistan Development Assistance 4 Afghanistan Economic Support Fund/Security Support Assistance 5 Afghanistan Food For Education 6 Afghanistan Global Health and Child Survival FY2000 FY2001 FY2002 FY2003 FY2004 FY2005 FY2006 1 NA NA 2586555 56501189 40215304 39817970 40856382 2 NA NA 2964313 NA 45635526 151334908 230501318 3 NA 4110478 8762080 54538965 180539337 193598227 212648440 4 NA 61144 31827014 341306822 1025522037 1157530168 1357750249 5 NA NA NA 3957312 2610006 3254408 386891 6 NA NA NA NA NA NA NA FY2007 FY2008 FY2009 1 72527069 28397435 NA 2 214505892 495539084 552524990 3 173134034 150529862 3675202 4 1266653993 1400237791 1418688520 5 NA NA NA 6 NA 63064912 1764252I want it set up so that each row represents a single country-program-year entry with the dollar amount stored in one column. To achieve this, I melt the data using melt from reshape2. > library(reshape2) > melt00 <- melt(Aid_00s, id.vars=c("Country.Name", "Program.Name"), + variable.name="Year", value.name="Dollars") > tail(melt00, 10) Country.Name 24521 Zimbabwe 24522 Zimbabwe 24523 Zimbabwe 24524 Zimbabwe 24525 Zimbabwe 24526 Zimbabwe 24527 Zimbabwe 24528 Zimbabwe 24529 Zimbabwe 24530 Zimbabwe Program.Name Year 24521 Migration and Refugee Assistance FY2009 24522 Narcotics Control FY2009 24523 Nonproliferation, Anti-Terrorism, Demining and Related FY2009 24524 Other Active Grant Programs FY2009 24525 Other Food Aid Programs FY2009 24526 Other State Assistance FY2009 24527 Other USAID Assistance FY2009 24528 Peace Corps FY2009 24529 Title I FY2009 24530 Title II FY2009 Dollars 24521 3627384 24522 NA 24523 NA 24524 7951032 24525 NA 24526 2193057 24527 41940500 24528 NA 24529 NA 24530 174572685The id.vars argument specifies which columns uniquely identify a row.After some manipulation of the Year column and aggregating, this is now prime for plotting. The plot uses faceting, letting you quickly see and understand the funding for each program over time.> library(scales) > # strip the "FY" out of the year column and convert it to numeric > melt00$Year <- as.numeric(str_sub(melt00$Year, start=3, 6)) > # aggregate the data so we have yearly numbers by program > meltAgg <- aggregate(Dollars ~ Program.Name + Year, data=melt00, + sum, na.rm=TRUE) > # just keep the first 10 characters of program name > # then it will fit in the plot > meltAgg$Program.Name <- str_sub(meltAgg$Program.Name, start=1, + end=10) > > ggplot(meltAgg, aes(x=Year, y=Dollars)) + + geom_line(aes(group=Program.Name)) + + facet_wrap(~ Program.Name) + + scale_x_continuous(breaks=seq(from=2000, to=2009, by=2)) + + theme(axis.text.x=element_text(angle=90, vjust=1, hjust=0)) + + scale_y_continuous(labels=multiple_format(extra=dollar, + multiple="B"))dcastNow that I have the foreign aid data melted, I cast it back into the wide format for illustration purposes. The function for this is dcast, and it has trickier arguments than melt. The first is the data to be used, in this case melt00. The second argument is a formula, where the left side specifies the columns that should remain columns and the right side specifies the columns that should become column names. The third argument is the column (as a character) that holds the values to be populated into the new columns, representing the unique values of the right side of the formula argument. > cast00 <- dcast(melt00, Country.Name + Program.Name ~ Year, + value.var="Dollars") > head(cast00) Country.Name Program.Name 2000 1 Afghanistan Child Survival and Health NA 2 Afghanistan Department of Defense Security Assistance NA 3 Afghanistan Development Assistance NA 4 Afghanistan Economic Support Fund/Security Support Assistance NA 5 Afghanistan Food For Education NA 6 Afghanistan Global Health and Child Survival NA 2001 2002 2003 2004 2005 2006 1 NA 2586555 56501189 40215304 39817970 40856382 2 NA 2964313 NA 45635526 151334908 230501318 3 4110478 8762080 54538965 180539337 193598227 212648440 4 61144 31827014 341306822 1025522037 1157530168 1357750249 5 NA NA 3957312 2610006 3254408 386891 6 NA NA NA NA NA NA 2007 2008 2009 1 72527069 28397435 NA 2 214505892 495539084 552524990 3 173134034 150529862 3675202 4 1266653993 1400237791 1418688520 5 NA NA NA 6 NA 63064912 1764252 AnalyticsSoftware DevelopmentR Language