Some define Statistics as the field that focuses on turning information into knowledge. The first step in that process is to summarize and describe the raw information - the data. In this lab, you will gain insight into public health by generating simple graphical and numerical summaries of a data set collected by the Centers for Disease Control and Prevention (CDC). As this is a large data set, along the way you’ll also learn the indispensable skills of data processing and subsetting.

Getting started

The Behavioral Risk Factor Surveillance System (BRFSS) is an annual telephone survey of 350,000 people in the United States. As its name implies, the BRFSS is designed to identify risk factors in the adult population and report emerging health trends. For example, respondents are asked about their diet and weekly physical activity, their HIV/AIDS status, possible tobacco use, and even their level of healthcare coverage. The BRFSS Web site ( contains a complete description of the survey, including the research questions that motivate the study and many interesting results derived from the data.

We will focus on a random sample of 20,000 people from the BRFSS survey conducted in 2000. While there are over 200 variables in this data set, we will work with a small subset.

We begin by loading the data set of 20,000 observations into the R workspace. After launching RStudio, enter the following command.


The data set cdc that shows up in your workspace is a data matrix, with each row representing a case and each column representing a variable. R calls this data format a data frame, which is a term that will be used throughout the labs.

To view the names of the variables, type the command


This returns the names genhlth, exerany, hlthplan, smoke100, height, weight, wtdesire, age, and gender. Each one of these variables corresponds to a question that was asked in the survey. For example, for genhlth, respondents were asked to evaluate their general health, responding either excellent, very good, good, fair or poor. The exerany variable indicates whether the respondent exercised in the past month (1) or did not (0). Likewise, hlthplan indicates whether the respondent had some form of health coverage (1) or did not (0). The smoke100 variable indicates whether the respondent had smoked at least 100 cigarettes in her lifetime. The other variables record the respondent’s height in inches, weight in pounds as well as their desired weight, wtdesire, age in years, and gender.

  1. How many cases are there in this data set? How many variables? For each variable, identify its data type (e.g. categorical, discrete).

We can have a look at the first few entries (rows) of our data with the command


and similarly we can look at the last few by typing


You could also look at all of the data frame at once by typing its name into the console, but that might be unwise here. We know cdc has 20,000 rows, so viewing the entire data set would mean flooding your screen. It’s better to take small peeks at the data with head, tail, the subsetting techniques that you’ll learn in a moment, or by clicking on the variable name in the Environment panel to bring up the spreadsheet viewer as we’ve previously seen.

Summaries and tables

The BRFSS questionnaire is a massive trove of information. A good first step in any analysis is to distill all of that information into a few summary statistics and graphics. As a simple example, the function favstats returns a numerical summary: minimum, first quartile, median, third quartile, maximum, mean, standard deviation, number of observations, and number of missing values. It is very similar to the summary command in its output. For weight this is

favstats(~weight, data = cdc)

As we have seen, R can function like a very fancy calculator. If you wanted to compute the interquartile range for the respondents’ weight, you could look at the output from the summary command above and then enter

190 - 140

R also has built-in functions to compute summary statistics one by one. For instance, to calculate the mean, median, and variance of weight, type

mean(~weight, data = cdc)
var(~weight, data = cdc)
median(~weight, data = cdc)

While it makes sense to describe a quantitative variable like weight in terms of these statistics, what about categorical data? We would instead consider the sample frequency or relative frequency distribution. The function tally does this for you by counting the number of times each kind of response was given. For example, to see the number of people who have smoked 100 cigarettes in their lifetime, type

tally(~smoke100, data = cdc)

or instead look at the relative frequency distribution by typing

tally(~smoke100, data = cdc, format = "proportion")

Notice how R automatically divides all entries in the table by 20,000 in the command above. This is similar to something we observed in the Introduction to R; when we multiplied or divided a vector with a number, R applied that action across entries in the vectors. As we see above, this also works for tables. Next, we make a bar chart of the entries in the table by putting the table inside the barchart command.

barchart(tally(~smoke100, data = cdc, margins=FALSE), horizontal=FALSE)

Notice what we’ve done here! We’ve computed the table of smoke100 and then immediately applied the graphical function, barchart. This is an important idea: R commands can be nested. You could also break this into two steps by typing the following:

smoke <- tally(~smoke100, data = cdc, margins=FALSE)
barchart(smoke, horizontal=FALSE)

Here, we’ve made a new object, a table, called smoke (the contents of which we can see by typing smoke into the console) and then used it in as the input for barchart. The special symbol <- performs an assignment, taking the output of one line of code and saving it into an object in your workspace.
This is another important idea that we’ll return to later.

  1. Create a numerical summary for height and age, and compute the interquartile range for each. Compute the relative frequency distribution for gender and exerany. How many males are in the sample? What proportion of the sample reports being in excellent health?

The tally command can be used to tabulate any number of variables that you provide. For example, to examine which participants have smoked across each gender, we could use the following to create the contingency table:

tally(gender ~ smoke100, data = cdc, format = "count")

Here, we see column labels of TRUE and FALSE. Recall that TRUE indicates a respondent has smoked at least 100 cigarettes. The rows refer to gender. To create a mosaic plot (no relation to the package name) of this table, we would enter the following command.

mosaicplot(tally(gender ~ smoke100, data = cdc))

We could have accomplished this in two steps by saving the table in one line and applying mosaicplot in the next (see the tally/barchart example above).

  1. What does the mosaic plot reveal about smoking habits and gender?

Interlude: How R thinks about data

We mentioned that R stores data in data frames, which you might think of as a type of spreadsheet. Each row is a different observation (a different respondent) and each column is a different variable (the first is genhlth, the second exerany and so on). We can see the size of the data frame next to the object name in the workspace or we can type


which will return the number of rows and columns. Now, if we want to access a subset of the full data frame, we can use row-and-column notation. For example, to see the sixth variable of the 567th respondent, use the format


which means we want the element of our data set that is in the 567th row (meaning the 567th person or observation) and the 6th column (in this case, weight). We know that weight is the 6th variable because it is the 6th entry in the list of variable names


To see the weights for the first 10 respondents we can type


In this expression, we have asked just for rows in the range 1 through 10. R uses the : to create a range of values, so 1:10 expands to 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. You can see this by entering


Finally, if we want all of the data for the first 10 respondents, type


By leaving out an index or a range (we didn’t type anything between the comma and the square bracket), we get all the columns. When starting out in R, this is a bit counterintuitive. As a rule, we omit the column number to see all columns in a data frame. Similarly, if we leave out an index or range for the rows, we would access all the observations, not just the 567th, or rows 1 through 10. Try the following to see the weights for all 20,000 respondents fly by on your screen


Recall that column 6 represents respondents’ weight, so the command above reported all of the weights in the data set. An alternative method to access the weight data is by using the select command. select is in the dplyr package, so we need to load it


When we start using dplyr with data, it is good coding practice to initialize your data with the function tbl_df(), which does nicer data previews, and also helps your code stay consistent when you move further into data science.

cdc <- tbl_df(cdc)

Once we’ve done this, we can preview our data by just typing its name in the console, and we won’t be subjected to the entire dataset like earlier.


dplyr lets you do many useful data manipulations, and it uses something called the ‘pipe’ operator, which looks like this: %>%. Many languages have pipe operators, and they allow functions to pass things along a chain of commands. This command will do the same thing as the cdc[,6] command above:

cdc %>% 

In R, we often read %>% as the English word ‘then.’ So, we could read this code as saying, ‘take the cdc data, then select the weight variable.’ We can add an additional command to our chain to see the weight for the 567th observation in the data set,

cdc %>% 
  select(weight) %>% 

We could also select a range of rows,

cdc %>% 
  select(weight) %>% 

The command above returns the same result as the cdc[1:10,6] command. Both row-and-column notation and select notation are widely used, which one you choose to use depends on your personal preference.

A little more on subsetting

It’s often useful to extract all individuals (cases) in a data set that have specific characteristics. We accomplish this through logical operators. Consider this expression:

cdc %>% 
  filter(gender == "m")

Note that equality in R is expressed with two equal signs, not one. It filters the dataset to only include the rows which have the gender variable set to m. You could also type something like

cdc %>% 
  filter(age > 30)

If we want to use this data again, we have to assign it to a new variable so we have access to it.

mdata <- 
  cdc %>% 
  filter(gender == "m")

will create a new data set called mdata that contains only the men from the cdc data set. In addition to finding it in your workspace alongside its dimensions, you can take a peek at the first several rows as usual


This new data set contains all the same variables but just under half the rows. It is also possible to tell R to keep only specific variables using select. For now, the important thing is that we can carve up the data based on values of one or more variables.

As an aside, you can use several of these logical operators together with & and |. The & is read “and” so that

m_and_over30 <- 
  cdc %>% 
  filter(gender == "m" & age > 30)

will give you the data for men over the age of 30 (i.e. males AND those over 30). The | character is read “or” so that

m_and_over30 <- 
  cdc %>% 
  filter(gender == "m" | age > 30)

can be read as take the cdc dataset, then filter the data for cases who are men OR over the age of 30. (Why that’s an interesting group is hard to say, but right now the mechanics of this are the important thing.) In principle, you may use as many “and” and “or” clauses as you like when forming a filter.

  1. Create a new object called under23_and_smoke that contains all observations of respondents under the age of 23 that have smoked 100 cigarettes in their lifetime. Write the command you used to create the new object as the answer to this exercise.

Quantitative data

With our filtering tools in hand, we’ll now return to the task of the day: making basic summaries of the BRFSS questionnaire. We’ve already looked at categorical data such as smoke and gender so now let’s turn our attention to quantitative data. Two common ways to visualize quantitative data are with box plots and histograms. We can construct a box plot for a single variable with the following command.

bwplot(~height, data = cdc)

In this case, the median is indicated with a dot, and not a thicker line. You can compare the locations of the components of the box by examining the summary statistics.

favstats(~height, data = cdc)

Confirm that the median and upper and lower quartiles reported in the numerical summary match those in the graph. The purpose of a boxplot is to provide a thumbnail sketch of a variable for the purpose of comparing across several categories. So we can, for example, compare the heights of men and women with

bwplot(height ~ gender, data = cdc)

The notation here is new. The ~ character can be read versus or as a function of. So we’re asking R to give us a box plots of heights where the groups are defined by gender.

Next let’s consider a new variable that doesn’t show up directly in this data set: Body Mass Index (BMI) ( BMI is a weight to height ratio and can be calculated as:

\[ BMI = \frac{weight~(lb)}{height~(in)^2} \cdot 703 \]

703 is the approximate conversion factor to change units from metric (meters and kilograms) to imperial (inches and pounds).

The following two lines first make a new object called bmi and then creates box plots of these values, defining groups by the variable genhlth.

cdc <- 
  cdc %>%
  mutate(bmi = (weight / height^2) * 703)
bwplot(bmi ~ genhlth, data = cdc)

Notice that the third line above is just some arithmetic, but it’s applied to all 20,000 numbers in the cdc data set. That is, for each of the 20,000 participants, we take their weight, divide by their height-squared and then multiply by 703. The result is 20,000 BMI values, one for each respondent. This is one reason why we like R: it lets us perform computations like this using very simple expressions.

When creating a similarly split boxplot for a categorical variable that has TRUE/FALSE values, we need to add as.factor() around the variable name. So for example, to compare bmi for smokers and non-smokers, we run:

bwplot(bmi ~ as.factor(smoke100), data = cdc)
  1. What does this box plot show? Pick another categorical variable from the data set and see how it relates to BMI. List the variable you chose, why you might think it would have a relationship to BMI, and indicate what the figure seems to suggest.

Finally, let’s make some histograms. We can look at the histogram for the age of our respondents with the command

histogram(~age, data = cdc)

Histograms are generally a very good way to see the shape of a single distribution, but that shape can change depending on how the data is split between the different bins. You can control the number of bins by adding an argument to the command. In the next two lines, we first make a default histogram of bmi and then one with 50 intervals.

histogram(~bmi, data = cdc)
histogram(~bmi, data = cdc, nint = 50)

Note that you can flip between plots that you’ve created by clicking the forward and backward arrows in the lower right region of RStudio, just above the plots. How do these two histograms compare?

At this point, we’ve done a good first pass at analyzing the information in the BRFSS questionnaire. We’ve found an interesting association between smoking and gender, and we can say something about the relationship between people’s assessment of their general health and their own BMI. We’ve also picked up essential computing tools – summary statistics, subsetting, and plots – that will serve us well throughout this course.

This is a product of OpenIntro that is released under a Creative Commons Attribution-ShareAlike 3.0 Unported. This lab was adapted for OpenIntro by Andrew Bray and Mine Çetinkaya-Rundel from a lab written by Mark Hansen of UCLA Statistics.