Today, we’re going to continue talking about data wrangling, motivated by issues you identified in your data appendices.

Cleaning up your environment

Now is a good time to clean up your Environment in R. I recommend clicking the broom icon on your Environment tab and removing all the objects from your environment. If you’re worried about a particular dataset, you can skip down to the bottom of the lab and learn how to use write_csv() to save data out of R. But in general, there is no danger to cleaning up your Environment.

Now, quit R and when it asks if you want to save your workspace image, select Yes. This is the last time I want you to select that! In general, you want to select No because you don’t want to preserve your environment.

Loading packages

We’re using a few additional packages today.

require(mosaic)
require(readr)
require(tidyr)

Importing data

To get the data files, go to http://www.science.smith.edu/~amcnamara/sds291/labs/sample_data.

doctorsDot = read.csv("sample_data/Physicians.csv")
str(doctorsDot)
## 'data.frame':    53 obs. of  2 variables:
##  $ State                            : Factor w/ 53 levels "Alabama","Alaska",..: 2 1 5 4 6 7 8 9 10 11 ...
##  $ Physicians.per.100.000.Population: Factor w/ 49 levels "1612","269","286",..: 45 9 6 20 20 21 37 1 46 18 ...

This example illustrates one issue with using read.csv()– it wants to make everything into factors, which we don’t want! I’ll keep that data around to show something later on.

doctors = read_csv("sample_data/Physicians.csv")
str(doctors)
## Classes 'tbl_df', 'tbl' and 'data.frame':    53 obs. of  2 variables:
##  $ State                            : chr  "Alaska" "Alabama" "Arkansas" "Arizona" ...
##  $ Physicians per 100,000 Population: chr  "514" "331" "321" "370" ...

See how this is better? It’s making the variables character vectors, which makes more sense.

Renaming variables

The variable names in this dataset are going to be hard to use,

names(doctors)
## [1] "State"                             "Physicians per 100,000 Population"

In order to run something like favstats(), we’d have to use backtics to write out the variable name.

favstats(~`Physicians per 100,000 Population`, data=doctors)
## Warning in FUN(eval(formula[[2]], data, .envir), ...): Auto-converting
## character to numeric.
## Warning in FUN(eval(formula[[2]], data, .envir), ...): NAs introduced by
## coercion
##  min  Q1 median     Q3  max     mean       sd  n missing
##  269 346    400 464.25 1612 429.0577 184.9278 52       1

So, let’s rename that variable.

doctors = doctors %>%
  rename(Physicians = `Physicians per 100,000 Population`)
names(doctors)
## [1] "State"      "Physicians"

That’s better!

Searching for strange issues in variables

But, we’ve still got a variable type issue. When we run summary statistics, R complains because there is something weird in the data. Let’s try tally()ing up the values in the data.

tally(~Physicians, data=doctors)
## Physicians
## 1612  269  286  287  310  321  322  323  331  332  335  336  338  340  348 
##    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1 
##  352  355  357  361  370  371  376  388  389  400  401  405  412  417  423 
##    1    1    1    2    2    1    1    1    1    2    1    1    1    1    1 
##  428  433  434  448  457  461  464  465  466  478  485  504  506  510  514 
##    1    1    1    1    1    1    1    1    1    2    1    1    1    1    1 
##  563  575  644  N/A 
##    1    1    1    1

Now we see it! There’s a strange NA value that R doesn’t know how to deal with. We can actually add this into our data-reading-in code.

doctors = read_csv("sample_data/Physicians.csv", na = c("", NA, "N/A"))

Of course, we have to redo our renaming.

doctors = doctors %>%
  rename(Physicians = `Physicians per 100,000 Population`)
favstats(~Physicians, data=doctors)
##  min  Q1 median     Q3  max     mean       sd  n missing
##  269 346    400 464.25 1612 429.0577 184.9278 52       1

Much better!

The importance of variable types

mean(~Physicians, data=doctors, na.rm=TRUE)
## [1] 429.0577

That makes sense, on average states have about 430 doctors. But what if we had used our data from read.csv()?

mean(~Physicians.per.100.000.Population, data=doctorsDot, na.rm=TRUE)
## Warning in mean.default(eval(formula[[2]], data, .envir), ...): argument is
## not numeric or logical: returning NA
## [1] NA

It doesn’t work because it’s a factor, so we might want to convert it to a numeric vector. The most intuitive thing (that doesn’t do what you want!!) would be

mean(~as.numeric(Physicians.per.100.000.Population), data=doctorsDot, na.rm=TRUE)
## [1] 25.07547

Now that number doesn’t make sense, but it’s also not so weird it would really catch your attention. It turns out what R does when you run as.numeric() on a factor variable is it turns each “label” (like 514 for the number of doctors in Alaska) into a number, based on where it is in the order of the factor levels (so, 514 turns into 45).

It turns out you can get the appropriate results by nesting as.numeric() and as.character(),

mean(~as.numeric(as.character(Physicians.per.100.000.Population)), data=doctorsDot, na.rm=TRUE)
## Warning: NAs introduced by coercion
## [1] 429.0577

but it’s much easier to just start with data from read_csv().

Filtering data

We might not want to keep all the data in our dataset. By looking at

tally(~State, data=doctors)
## State
##        Alabama         Alaska         All US        Arizona       Arkansas 
##              1              1              1              1              1 
##     California       Colorado    Connecticut           D.C.       Delaware 
##              1              1              1              1              1 
##        Florida        Georgia         Hawaii          Idaho       Illinois 
##              1              1              1              1              1 
##        Indiana           Iowa         Kansas       Kentucky      Louisiana 
##              1              1              1              1              1 
##          Maine       Maryland  Massachusetts       Michigan      Minnesota 
##              1              1              1              1              1 
##    Mississippi       Missouri        Montana       Nebraska         Nevada 
##              1              1              1              1              1 
##  New Hampshire     New Jersey     New Mexico       New York North Carolina 
##              1              1              1              1              1 
##   North Dakota           Ohio       Oklahoma         Oregon   Pennsylvania 
##              1              1              1              1              1 
##    Puerto Rico   Rhode Island South Carolina   South Dakota      Tennessee 
##              1              1              1              1              1 
##          Texas           Utah        Vermont       Virginia     Washington 
##              1              1              1              1              1 
##  West Virginia      Wisconsin        Wyoming 
##              1              1              1

I can see that All US is included, as is Puerto Rico. Let’s remove those.

doctors = doctors %>% 
  filter(State != "All US", State != "Puerto Rico")
tally(~State, data=doctors)
## State
##        Alabama         Alaska        Arizona       Arkansas     California 
##              1              1              1              1              1 
##       Colorado    Connecticut           D.C.       Delaware        Florida 
##              1              1              1              1              1 
##        Georgia         Hawaii          Idaho       Illinois        Indiana 
##              1              1              1              1              1 
##           Iowa         Kansas       Kentucky      Louisiana          Maine 
##              1              1              1              1              1 
##       Maryland  Massachusetts       Michigan      Minnesota    Mississippi 
##              1              1              1              1              1 
##       Missouri        Montana       Nebraska         Nevada  New Hampshire 
##              1              1              1              1              1 
##     New Jersey     New Mexico       New York North Carolina   North Dakota 
##              1              1              1              1              1 
##           Ohio       Oklahoma         Oregon   Pennsylvania   Rhode Island 
##              1              1              1              1              1 
## South Carolina   South Dakota      Tennessee          Texas           Utah 
##              1              1              1              1              1 
##        Vermont       Virginia     Washington  West Virginia      Wisconsin 
##              1              1              1              1              1 
##        Wyoming 
##              1

That did it.

Merging/joining data

Let’s load in some more data. This data is about police killings in different states

policekillings = read_csv("sample_data/policekillings.csv")
str(policekillings)
## Classes 'tbl_df', 'tbl' and 'data.frame':    47 obs. of  2 variables:
##  $ state : chr  "AK" "AL" "AR" "AZ" ...
##  $ deaths: int  2 8 4 25 74 12 1 1 2 29 ...

It doesn’t have many data issues. But, say we wanted to merge it with our physicians data. We need a variable to “join on.” That is, something that R can use to match up which row in doctors goes with which row in policekillings. Unfortunately, doctors has state names written out and policekillings just has the state abbreviations. We need to change one to match the other.

policekillings = policekillings %>%
  mutate(state = state.name[match(state, state.abb)])
str(policekillings)
## Classes 'tbl_df', 'tbl' and 'data.frame':    47 obs. of  2 variables:
##  $ state : chr  "Alaska" "Alabama" "Arkansas" "Arizona" ...
##  $ deaths: int  2 8 4 25 74 12 1 1 2 29 ...

This works because state.abb is a list of all the state abbreviations and state.name is a list of all the full state names. Both come built in to R.

Now, we can do some joining. After all, if we want to understand the relationship between physicians and police killings, we have to have both variables in the same data frame. The command that allows you to combine data frames is a “join”, which corresponds to the database operation called a JOIN.

When two database tables (or data frames) are joined, the rows of one table get matched to the rows of the other. Computers are exceptionally better suited for this task than humans, but the computer needs to be told what the criteria is for matching up the rows. The variable(s) upon which the matching is based are called a key. Both tables must contain the key columns, but there are variety of ways in which the matching can be done.

Joins can be complicated, so you may want to look at a visual explanation:

Before we start, let’s consider the dimensions of our two data frames.

dim(policekillings)
## [1] 47  2
dim(doctors)
## [1] 51  2

They don’t have the same number of rows, so we should think about how large we want our data to be at the end of our join. Do we want to keep all 51 rows in doctors and match elements from policekillings, filling with NA values where there aren’t matches? Or, are we more interested in policekillings and we want to just match the 47 states with elements from doctors?

In databases, the default JOIN type is INNER JOIN. In this case only the rows that are present in both data frames get returned. In R, I find I most often use a left_join, which retains all of the records from the first data frame, regardless of whether there is a match in the second data frame.

There are also A right joins, which retain all of the records from the second data frame, regardless of whether there is a match in the first data frame, and full joins, which contain all of the records from both data frames, regardless of whether there was a match. [Note that the number of rows returned can exceed the number of rows in either of the two original data frames.]

For this application, let’s try a left_join.

joinedData =  left_join(policekillings, doctors)
## Error: `by` required, because the data sources have no common variables

This throws an error, because it is expecting there to be a variable with the same name in each data set. To get it to work, we need to specify which variables to join on.

joinedData =  left_join(policekillings, doctors, by = c("state"= "State"))
str(joinedData)
## Classes 'tbl_df', 'tbl' and 'data.frame':    47 obs. of  3 variables:
##  $ state     : chr  "Alaska" "Alabama" "Arkansas" "Arizona" ...
##  $ deaths    : int  2 8 4 25 74 12 1 1 2 29 ...
##  $ Physicians: int  514 331 321 370 370 371 464 NA 563 357 ...

Notice that this dataset only has 47 observations, because there were only 47 in policekillings (the “left” in our left_join). We could have reversed the order of the datasets to get something slightly different.

joinedData =  left_join(doctors, policekillings, by = c("State"= "state"))
str(joinedData)
## Classes 'tbl_df', 'tbl' and 'data.frame':    51 obs. of  3 variables:
##  $ State     : chr  "Alaska" "Alabama" "Arkansas" "Arizona" ...
##  $ Physicians: int  514 331 321 370 370 371 464 1612 563 357 ...
##  $ deaths    : int  2 8 4 25 74 12 1 NA 2 29 ...

Now, the data has 51 observations! (We could have gotten the same result by running right_join with the data specified the same way as our first join.)

Filtering and recoding data

For another example, consider this data about political opinions.

politics = read_csv("sample_data/politics.csv")
str(politics)
## Classes 'tbl_df', 'tbl' and 'data.frame':    3564 obs. of  12 variables:
##  $                : int  4178 4761 4299 4857 4892 3979 4454 4946 4479 4356 ...
##  $ Left_Right_Wing: int  3 3 5 7 5 4 0 6 3 0 ...
##  $ BirthYear      : int  1976 1972 1977 1970 1981 1964 1975 1995 1965 1975 ...
##  $ Income         : int  4 4 5 2 5 5 3 3 3 2 ...
##  $ Religiousity   : int  3 2 1 2 3 2 3 3 1 3 ...
##  $ Education      : int  4 8 8 3 4 4 7 4 8 4 ...
##  $ Country        : chr  "Austria" "Austria" "Austria" "Austria" ...
##  $ Location       : int  1 4 1 2 3 2 2 1 2 1 ...
##  $ Gender         : int  1 2 1 1 1 1 2 1 1 2 ...
##  $ Occupation     : int  722 233 216 723 522 121 226 441 216 412 ...
##  $ Marital_Status : int  4 1 1 4 4 1 1 4 4 3 ...
##  $ Age            : int  40 44 39 46 35 52 41 21 51 41 ...

We might want to filter the data to only include the observations from Bulgaria. So, we could use a filter() to filter out the data.

politics = politics %>%
  filter(Country == "Bulgaria")
## Error in filter_impl(.data, quo): Evaluation error: attempt to use zero-length variable name.

This doesn’t work, and the error message is a little obscure. I had to google it! Looking at those results, I saw one that mentioned filter(), so I checked it out. It seems like the problem is empty variable names, which we have in this dataset.

names(politics)
##  [1] ""                "Left_Right_Wing" "BirthYear"      
##  [4] "Income"          "Religiousity"    "Education"      
##  [7] "Country"         "Location"        "Gender"         
## [10] "Occupation"      "Marital_Status"  "Age"

To fix this, we can do a rather strange renaming. Now, our filter works!

politics = politics %>%
  rename(obs = )
str(politics)
politics = politics %>%
  filter(Country == "Bulgaria")

We also might want to recode Left_Right_Wing to be a binary variable.

politics = politics %>%
  mutate(LRbinary = if_else(Left_Right_Wing>5, "right", "left"))
str(politics)

String operations

I didn’t have a great example from your data appendices here, but string operations are often important for data cleaning. What will help you here is the stringr package and the gsub() function. The parse_numeric() function from readr can also be very helpful.

This code removes Xs from the front of the Year variable.

library(stringr)
popLong = popLong %>%
  mutate(Year = as.numeric(gsub("X", "", Year)))

This code fixes an issue in the population column, which contains commas that separate the thousands places in what are supposed to be numeric values. We can fix this by using the gsub() command to remove them all at once.

popLong = popLong %>%
  mutate(population = as.numeric(gsub(",", "", population)))

Exporting the Data

Now that we’ve spent all this time getting our data into the right format, we want to write it to a file so that we never have to do this again! The opposite of read_csv is write_csv and it does exactly that.

write_csv(policekillings, "policekillings.csv")

The file should then show up in your “Files” window, where you can “Export” it to your local computer if you like.

Closing down

Now, close your RStudio session and say Don’t Save to the environment question!