Fisseha Berhane, PhD

Data Scientist

443-970-2353 fisseha@jhu.edu CV Resume Linkedin GitHub twitter twitter

My Two Favorite Packages for Data Manipulation in R


dplyr and data.table are so awesome as they make data manipulation more fun. Both packages have their strengths. While dplyr is more elegant and resembles natural language, data.table is succinct and we can do a lot with data.table in just a single line. Further, data.table is, in some cases, faster (see benchmark here) and it may be a go-to package when performance and memory are the constraints. You can read comparison of dplyr and data.table from Stack Overflow and Quora.

You can get reference manual and vignettes for data.table here and for dplyr here.


You can get the code for this blog post on GitHub.

I am a long time dplyr and data.table user for my data manipulation tasks. For someone who knows one of these packages, I thought it could help to show codes that perform the same tasks in both packages to help them quickly study the other. If you know either package and have interest to study the other, this post is for you.

dplyr has 5 verbs. These verbs make up the majority of the data manipulation tasks we perform.

  • Select: used to select one or more columns
  • Filter: used to select some rows based on specific criteria
  • Arrange: used to sort data based on one or more columns in ascending or descending order
  • Mutate: used to add new columns to our data
  • Summarise: used to create chunks from our data.

data.table has a very succint general format:

DT[i, j, by], which is interpreted as “Take DT, subset rows using i, then calculate j grouped by by”.

As shown below, i, j, by correspnd to where, select | update, group by, respectively, in SQL.

R: i j by

SQL: WHERE SELECT | UPDATE GROUP BY

Load R Packages

In [148]:
library(dplyr)
library(data.table)
library(lubridate)
library(jsonlite)
library(tidyr)
library(ggplot2)
library(compare)

Download data

The data we will use here is from DATA.GOV. It is Medicare Hospital Spending by Claim and it can be downloaded from here.

Let's download the data in JSON format using the fromJSON function from the jsonlite package.

Since JSON is a very common data format used for asynchronous browser/server communication, it is good if you understand the lines of code below used to get the data. You can get an introductory tutorial on how to use the jsonlite package to work with JSON data here. However, if you want to focus only on the data.table and dplyr commands, you can safely just run the codes in the two cells below and ignore the details.

In [215]:
spending=fromJSON("https://data.medicare.gov/api/views/nrth-mfg3/rows.json?accessType=DOWNLOAD")
names(spending)
Out[215]:
  1. "meta"
  2. "data"
In [216]:
meta=spending$meta
hospital_spending=data.frame(spending$data)
colnames(hospital_spending)=make.names(meta$view$columns$name)
hospital_spending=select(hospital_spending,-c(sid:meta))
In [217]:
glimpse(hospital_spending)
Observations: 70598
Variables:
$ Hospital.Name                       (fctr) SOUTHEAST ALABAMA MEDICAL CENT...
$ Provider.Number.                    (fctr) 010001, 010001, 010001, 010001...
$ State                               (fctr) AL, AL, AL, AL, AL, AL, AL, AL...
$ Period                              (fctr) 1 to 3 days Prior to Index Hos...
$ Claim.Type                          (fctr) Home Health Agency, Hospice, I...
$ Avg.Spending.Per.Episode..Hospital. (fctr) 12, 1, 6, 160, 1, 6, 462, 0, 0...
$ Avg.Spending.Per.Episode..State.    (fctr) 14, 1, 6, 85, 2, 9, 492, 0, 0,...
$ Avg.Spending.Per.Episode..Nation.   (fctr) 13, 1, 5, 117, 2, 9, 532, 0, 0...
$ Percent.of.Spending..Hospital.      (fctr) 0.06, 0.01, 0.03, 0.84, 0.01, ...
$ Percent.of.Spending..State.         (fctr) 0.07, 0.01, 0.03, 0.46, 0.01, ...
$ Percent.of.Spending..Nation.        (fctr) 0.07, 0.00, 0.03, 0.58, 0.01, ...
$ Measure.Start.Date                  (fctr) 2014-01-01T00:00:00, 2014-01-0...
$ Measure.End.Date                    (fctr) 2014-12-31T00:00:00, 2014-12-3...

As shown above, all columns are imported as factors and let's change the columns that contain numeric values to numeric.

In [218]:
cols = 6:11; # These are the columns to be changed to numeric.
hospital_spending[,cols] <- lapply(hospital_spending[,cols], as.character)
hospital_spending[,cols] <- lapply(hospital_spending[,cols], as.numeric)

The last two columns are measure start date and measure end date. So, let's use the lubridate package to correct the classes of these columns.

In [219]:
cols = 12:13; # These are the columns to be changed to dates.
hospital_spending[,cols] <- lapply(hospital_spending[,cols], ymd_hms)

Now, let's check if the columns have the classes we want.

In [220]:
sapply(hospital_spending,class)
Out[220]:
$Hospital.Name
"factor"
$Provider.Number.
"factor"
$State
"factor"
$Period
"factor"
$Claim.Type
"factor"
$Avg.Spending.Per.Episode..Hospital.
"numeric"
$Avg.Spending.Per.Episode..State.
"numeric"
$Avg.Spending.Per.Episode..Nation.
"numeric"
$Percent.of.Spending..Hospital.
"numeric"
$Percent.of.Spending..State.
"numeric"
$Percent.of.Spending..Nation.
"numeric"
$Measure.Start.Date
  1. "POSIXct"
  2. "POSIXt"
$Measure.End.Date
  1. "POSIXct"
  2. "POSIXt"

Create data table.

We can create a data.table using the data.table() function.

In [221]:
hospital_spending_DT = data.table(hospital_spending)
In [222]:
class(hospital_spending_DT)
Out[222]:
  1. "data.table"
  2. "data.frame"

Select certain columns of data

To select columns, we use the verb select in dplyr. In data.table, on the other hand, we can specify the column names.

Selecting one variable

Let's selet the "Hospital Name" variable

In [193]:
from_dplyr = select(hospital_spending, Hospital.Name)
from_data_table = hospital_spending_DT[,.(Hospital.Name)]

Now, let's compare if the results from dplyr and data.table are the same.

In [194]:
compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[194]:
TRUE
  dropped attributes

Removing one variable

In [195]:
from_dplyr = select(hospital_spending, -Hospital.Name)
from_data_table = hospital_spending_DT[,!c("Hospital.Name"),with=FALSE]
compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[195]:
TRUE
  dropped attributes

we can also use := function which modifies the input data.table by reference.

We will use the copy() function, which deep copies the input object and therefore any subsequent update by reference operations performed on the copied object will not affect the original object.

In [196]:
DT=copy(hospital_spending_DT)
DT=DT[,Hospital.Name:=NULL]
"Hospital.Name"%in%names(DT)
Out[196]:
FALSE

We can also remove many variables at once similarly:

In [197]:
DT=copy(hospital_spending_DT)
DT=DT[,c("Hospital.Name","State","Measure.Start.Date","Measure.End.Date"):=NULL]
c("Hospital.Name","State","Measure.Start.Date","Measure.End.Date")%in%names(DT)
Out[197]:
  1. FALSE
  2. FALSE
  3. FALSE
  4. FALSE

Selecting multiple variables

Let's select the variables Hospital.Name,State,Measure.Start.Date,and Measure.End.Date.

In [198]:
from_dplyr = select(hospital_spending, Hospital.Name,State,Measure.Start.Date,Measure.End.Date)
from_data_table = hospital_spending_DT[,.(Hospital.Name,State,Measure.Start.Date,Measure.End.Date)]
compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[198]:
TRUE
  dropped attributes

Dropping multiple variables

Now, let's remove the variables Hospital.Name,State,Measure.Start.Date,and Measure.End.Date from the original data frame hospital_spending and the data.table hospital_spending_DT.

In [199]:
from_dplyr = select(hospital_spending, -c(Hospital.Name,State,Measure.Start.Date,Measure.End.Date))
from_data_table = hospital_spending_DT[,!c("Hospital.Name","State","Measure.Start.Date","Measure.End.Date"),with=FALSE]
compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[199]:
TRUE
  dropped attributes

dplyr has functions contains(), starts_with() and, ends_with() which we can use with the verb select. In data.table, we can use regular expressions. Let's select columns that contain the word Date to demonstrate by example.

In [200]:
from_dplyr = select(hospital_spending,contains("Date"))
from_data_table = subset(hospital_spending_DT,select=grep("Date",names(hospital_spending_DT)))
compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[200]:
TRUE
  dropped attributes
In [201]:
names(from_dplyr)
Out[201]:
  1. "Measure.Start.Date"
  2. "Measure.End.Date"

Rename columns

In [223]:
setnames(hospital_spending_DT,c("Hospital.Name", "Measure.Start.Date","Measure.End.Date"),
                           c("Hospital","Start_Date","End_Date"))
names(hospital_spending_DT)
Out[223]:
  1. "Hospital"
  2. "Provider.Number."
  3. "State"
  4. "Period"
  5. "Claim.Type"
  6. "Avg.Spending.Per.Episode..Hospital."
  7. "Avg.Spending.Per.Episode..State."
  8. "Avg.Spending.Per.Episode..Nation."
  9. "Percent.of.Spending..Hospital."
  10. "Percent.of.Spending..State."
  11. "Percent.of.Spending..Nation."
  12. "Start_Date"
  13. "End_Date"
In [203]:
hospital_spending = rename(hospital_spending,Hospital= Hospital.Name, Start_Date=Measure.Start.Date,End_Date=Measure.End.Date)

compare(hospital_spending,hospital_spending_DT, allowAll=TRUE)
Out[203]:
TRUE
  dropped attributes

Filtering data to select certain rows

To filter data to select specific rows, we use the verb filter from dplyr with logical statements that could include regular expressions. In data.table, we need the logical statements only.

Filter based on one variable

In [204]:
from_dplyr = filter(hospital_spending,State=='CA') # selecting rows for California
from_data_table = hospital_spending_DT[State=='CA']
compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[204]:
TRUE
  dropped attributes

Filter based on multiple variables

In [205]:
from_dplyr = filter(hospital_spending,State=='CA' & Claim.Type!="Hospice") 
from_data_table = hospital_spending_DT[State=='CA' & Claim.Type!="Hospice"]
compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[205]:
TRUE
  dropped attributes
In [206]:
from_dplyr = filter(hospital_spending,State %in% c('CA','MA',"TX")) 
from_data_table = hospital_spending_DT[State %in% c('CA','MA',"TX")]
unique(from_dplyr$State)
Out[206]:
  1. CA
  2. MA
  3. TX
In [207]:
compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[207]:
TRUE
  dropped attributes

Order data

We use the verb arrange in dplyr to order the rows of data. We can order the rows by one or more variables. If we want descending, we have to use desc as shown in the examples.

The examples are self-explanatory on how to sort in ascending and descending order.

Let's sort using one variable.

Ascending
In [208]:
from_dplyr = arrange(hospital_spending, State)

from_data_table = setorder(hospital_spending_DT, State)

compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[208]:
TRUE
  dropped attributes
Descending
In [209]:
from_dplyr = arrange(hospital_spending, desc(State))

from_data_table = setorder(hospital_spending_DT, -State)

compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[209]:
TRUE
  dropped attributes
Sorting with multiple variables

Let's sort with State in ascending order and End_Date in descending order

In [210]:
from_dplyr = arrange(hospital_spending, State,desc(End_Date))

from_data_table = setorder(hospital_spending_DT, State,-End_Date)

compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[210]:
TRUE
  dropped attributes

Adding/updating column(s)

In dplyr we use the function mutate() to add columns. In data.table, we can Add/update a column by reference using := in one line.

In [172]:
from_dplyr = mutate(hospital_spending, diff=Avg.Spending.Per.Episode..State. - Avg.Spending.Per.Episode..Nation.)

from_data_table = copy(hospital_spending_DT)
from_data_table = from_data_table[,diff := Avg.Spending.Per.Episode..State. - Avg.Spending.Per.Episode..Nation.]

compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[172]:
TRUE
  sorted
  renamed rows
  dropped row names
  dropped attributes
In [225]:
from_dplyr = mutate(hospital_spending, diff1=Avg.Spending.Per.Episode..State. - Avg.Spending.Per.Episode..Nation.,
                                        diff2=End_Date-Start_Date)

from_data_table = copy(hospital_spending_DT)
from_data_table = from_data_table[,c("diff1","diff2") := list(Avg.Spending.Per.Episode..State. - Avg.Spending.Per.Episode..Nation.,
                                                             diff2=End_Date-Start_Date)]

compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[225]:
TRUE
  dropped attributes

Summarising columns

We can use the summarize() function from dplyr to create summary statistics

In [174]:
summarize(hospital_spending,mean=mean(Avg.Spending.Per.Episode..Nation.))
Out[174]:
mean
18.772727
In [175]:
hospital_spending_DT[,.(mean=mean(Avg.Spending.Per.Episode..Nation.))]
Out[175]:
mean
18.772727
In [176]:
summarize(hospital_spending,mean=mean(Avg.Spending.Per.Episode..Nation.),
                            maximum=max(Avg.Spending.Per.Episode..Nation.),
                            minimum=min(Avg.Spending.Per.Episode..Nation.),
                            median=median(Avg.Spending.Per.Episode..Nation.))
Out[176]:
meanmaximumminimummedian
18.7727271918.5
In [177]:
hospital_spending_DT[,.(mean=mean(Avg.Spending.Per.Episode..Nation.),
                        maximum=max(Avg.Spending.Per.Episode..Nation.),
                        minimum=min(Avg.Spending.Per.Episode..Nation.),
                        median=median(Avg.Spending.Per.Episode..Nation.))]
Out[177]:
meanmaximumminimummedian
18.7727271918.5

We can calculate our summary statistics for some chunks separately. We use the function group_by() in dplyr and in data.table, we simply provide by.

In [178]:
head(hospital_spending_DT[,.(mean=mean(Avg.Spending.Per.Episode..Hospital.)),by=.(Hospital)])
Out[178]:
Hospitalmean
1PROVIDENCE ALASKA MEDICAL CENTER3904.773
2BARTLETT REGIONAL HOSPITAL3939.409
3MAT-SU REGIONAL MEDICAL CENTER3633.182
4MT EDGECUMBE HOSPITAL1894.5
5FAIRBANKS MEMORIAL HOSPITAL3281.455
6ALASKA REGIONAL HOSPITAL3371.591
In [179]:
mygroup= group_by(hospital_spending,Hospital) 

from_dplyr = summarize(mygroup,mean=mean(Avg.Spending.Per.Episode..Hospital.))
from_data_table=hospital_spending_DT[,.(mean=mean(Avg.Spending.Per.Episode..Hospital.)),
                                     by=.(Hospital)]

compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[179]:
TRUE
  sorted
  renamed rows
  dropped row names
  dropped attributes

We can also provide more than one grouping condition.

In [180]:
head(hospital_spending_DT[,.(mean=mean(Avg.Spending.Per.Episode..Hospital.)),
                          by=.(Hospital,State)])
Out[180]:
HospitalStatemean
1PROVIDENCE ALASKA MEDICAL CENTERAK3904.773
2BARTLETT REGIONAL HOSPITALAK3939.409
3MAT-SU REGIONAL MEDICAL CENTERAK3633.182
4MT EDGECUMBE HOSPITALAK1894.5
5FAIRBANKS MEMORIAL HOSPITALAK3281.455
6ALASKA REGIONAL HOSPITALAK3371.591
In [181]:
mygroup= group_by(hospital_spending,Hospital,State)

from_dplyr = summarize(mygroup,mean=mean(Avg.Spending.Per.Episode..Hospital.))
from_data_table=hospital_spending_DT[,.(mean=mean(Avg.Spending.Per.Episode..Hospital.)),
                                     by=.(Hospital,State)]

compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[181]:
TRUE
  sorted
  renamed rows
  dropped row names
  dropped attributes

Chaining

With both dplyr and data.table, we can chain functions in succession. In dplyr, we use pipes from the magrittr package with %>% which is really cool. %>% takes the output from one function and feeds it to the first argument of the next function. In data.table, we can use %>% or [ for chaining.

In [182]:
from_dplyr=hospital_spending%>%group_by(Hospital,State)%>%summarize(mean=mean(Avg.Spending.Per.Episode..Hospital.))

from_data_table=hospital_spending_DT[,.(mean=mean(Avg.Spending.Per.Episode..Hospital.)),
                                     by=.(Hospital,State)]

compare(from_dplyr,from_data_table, allowAll=TRUE)
Out[182]:
TRUE
  sorted
  renamed rows
  dropped row names
  dropped attributes
In [228]:
hospital_spending%>%group_by(State)%>%summarize(mean=mean(Avg.Spending.Per.Episode..Hospital.))%>%
arrange(desc(mean))%>%head(10)%>%
        mutate(State = factor(State,levels = State[order(mean,decreasing =TRUE)]))%>%
          ggplot(aes(x=State,y=mean))+geom_bar(stat='identity',color='darkred',fill='skyblue')+
          xlab("")+ggtitle('Average Spending Per Episode by State')+
          ylab('Average')+ coord_cartesian(ylim = c(3800, 4000))
In [229]:
hospital_spending_DT[,.(mean=mean(Avg.Spending.Per.Episode..Hospital.)),
                                     by=.(State)][order(-mean)][1:10]%>% 
            mutate(State = factor(State,levels = State[order(mean,decreasing =TRUE)]))%>%
           ggplot(aes(x=State,y=mean))+geom_bar(stat='identity',color='darkred',fill='skyblue')+
          xlab("")+ggtitle('Average Spending Per Episode by State')+
          ylab('Average')+ coord_cartesian(ylim = c(3800, 4000))

Summary

In this blog post, we saw how we can perform the same tasks using data.table and dplyr packages. Both packages have their strengths. While dplyr is more elegant and resembles natural language, data.table is succinct and we can do a lot with data.table in just a single line. Further, data.table is in some cases faster (see benchmark here) and it may be a go-to package when performance and memory are the constraints.





comments powered by Disqus