Exercise 5: Exploratory analysis and graphics

 

Read Chapter 4 to help you complete the questions in this exercise.

 

1. As in previous exercises, either create a new R script or continue with your previous R script in your RStudio Project. Again, make sure you include any metadata you feel is appropriate (title, description of task, date of creation etc) and don’t forget to comment out your metadata with a # at the beginning of the line.

 

2. You do not need to download anything for this exercise. You already have ‘cardiacdata.txt’ in the data directory of your RStudio Project, from Exercise 3. If for some reason you haven’t, go back to the Data link, download ‘cardiacdata.xlsx’ and save it as a tab delimited file called ‘cardiacdata.txt’ as you did before.

 

3. A quick reminder of what these data are. They come from a cohort study of the risk factors for cardiovascular disease, in which 163 adults aged between 55 and 75 were examined and a range of measurements taken: age and sex, systolic and diastolic blood pressure (mmHg), body mass index (kg m-2), total cholesterol, HDL cholesterol and triglyceride (all mmol l-1), units of alcohol drunk in the previous week, and smoking status. Note that you are importing the original file again, not the cleaned version you exported at the end of Exercise 4. Starting from the raw data every time, and doing your cleaning in a script, is what makes an analysis reproducible.

 

4. Import the ‘cardiacdata.txt’ file into R using the read.table() function and assign it to a variable named cardiac. Use the str() function to display the structure of the dataset. As you found in Exercise 3, the sex and smoking variables are coded as integers, but they are really categories. Create a new variable in the cardiac dataframe for each of them, recoded as a factor with meaningful labels (sex is 1 for Female and 2 for Male; smoking is 1 for Current, 2 for Ex and 3 for Never). Use the str() function again to check the coding of these new variables. Almost every plotting function in this exercise treats a factor differently from a number, so this step is not optional housekeeping - get it wrong and your boxplots will come out as nonsense.

cardiac <- read.table('data/cardiacdata.txt', header = TRUE, sep = "\t", stringsAsFactors = TRUE)

str(cardiac)

# recode the two categorical variables as factors, keeping the originals
cardiac$Fsex <- factor(cardiac$sex, levels = c(1, 2),
                       labels = c("Female", "Male"))

cardiac$Fsmoking <- factor(cardiac$smoking, levels = c(1, 2, 3),
                           labels = c("Current", "Ex", "Never"))

str(cardiac)

#  $ Fsex    : Factor w/ 2 levels "Female","Male": 2 2 2 2 2 2 2 1 1 2 ...
#  $ Fsmoking: Factor w/ 3 levels "Current","Ex",..: NA NA NA NA NA NA NA 1 1 1 ...

 

5. How many patients are there in each combination of smoking status and sex (hint: remember the table() function?)? Don’t forget to use the factor recoded versions of these variables. Is the split between the smoking categories similar for women and men? Are any of the combinations small enough to worry about if you wanted to compare them? Finally, run the table again with the argument useNA = "ifany" so that the patients with no recorded smoking status appear as a row of their own. How many are there, and is there anything they have in common?

table(cardiac$Fsmoking, cardiac$Fsex)

  #           Female Male
  # Current       26   24
  # Ex            15   37
  # Never         37   17

# the pattern is almost reversed between the sexes: most of the men are
# ex-smokers, most of the women have never smoked. The smallest cell has 15
# patients, which is small but not unusable.

table(cardiac$Fsmoking, cardiac$Fsex, useNA = "ifany")

  #           Female Male
  # Current       26   24
  # Ex            15   37
  # Never         37   17
  # <NA>           0    7

# all 7 of the patients with no smoking status are men, and not one is a
# woman. Missing values spread evenly through a dataset are a nuisance;
# missing values concentrated in one group are a bias, because every analysis
# that quietly drops them drops men only. You cannot tell which you have
# without looking, and `useNA = "ifany"` is how you look.

 

6. The humble cleveland dotplot is a great way of identifying if you have potential outliers in continuous variables (see Section 4.2.4). Create dotplots (using the dotchart() function) for the following variables; bmi, systolic, tchol and alcohol. Do these variables contain any unusually large or small observations? Don’t forget, if you prefer to create a single figure with all 4 plots you can always split your plotting device into 2 rows and 2 columns (see Section 4.4 of the book).

par(mfrow = c(2, 2))
dotchart(cardiac$bmi, main = "bmi")
dotchart(cardiac$systolic, main = "systolic")
dotchart(cardiac$tchol, main = "total cholesterol")
dotchart(cardiac$alcohol, main = "alcohol")

# the bmi plot is the striking one: a single point sits so far to the right
# that every other patient is squashed into a narrow strip on the left. You
# cannot see the shape of the bmi distribution at all, because one value is
# setting the scale for all 163.

 

7. That single point over on the right of the bmi dotplot is the value you met in Exercise 4, when you ran summary() and found a maximum of 514.60. A body mass index of 514 isn’t possible. It’s almost certainly 51.46 with the decimal point in the wrong place, although we can’t confirm that. Notice how much easier the dotplot has made this to spot. In Exercise 4 you had to work your way through the summary() output and check the minimum and maximum of each variable in turn, whereas here it stands out as soon as you draw the plot. That’s a good reason to make a dotplot of each of your continuous variables early on.

Remember that you imported the raw file again in Q4, so none of the cleaning you did in Exercise 4 is in this dataframe. Redo all three of those fixes now. Use the which() function to identify which observation the impossible bmi is, confirm the value with the square bracket [ ] notation, then set that bmi, the single triglyceride of 0 and the two hdlchol values of 0 to NA, exactly as you did in Exercise 4. Redraw the bmi dotchart. Now look again at all four plots from Q6. Several points still sit well away from the rest. Which ones, and what should you do about them?

which(cardiac$bmi > 100)
cardiac$bmi[161]

# the same three fixes you made in Exercise 4 Q1. Your cleaning lives in your
# script, not in the data file, so it has to run again every time you import
# the raw data. That is exactly what makes it reproducible, and it is the
# reason for writing it down rather than editing the spreadsheet.
cardiac$bmi[cardiac$bmi > 100] <- NA
cardiac$triglyceride[cardiac$triglyceride == 0] <- NA
cardiac$hdlchol[cardiac$hdlchol == 0] <- NA

dotchart(cardiac$bmi, main = "bmi")

# with that one value gone the axis rescales and you can see the rest of the
# patients properly: most between about 20 and 30, thinning out to four above
# 35, the largest of them at 44.44.

# What else stands out? One patient with a bmi of 44.44, one with a systolic
# pressure of 230 mmHg, and in the alcohol plot a handful of patients reporting
# 41, 64 and 82 units in a week when the median is 2.

# What should you do about them? NOTHING. Every one of those values is
# perfectly possible. A BMI of 44 is severe obesity, a systolic pressure of 230
# is a hypertensive crisis, and 82 units a week is a great deal of alcohol but
# people really do drink that much. These are not errors, they are patients.
# The difference between this question and the last one is the difference
# between a value that CANNOT be right and a value you did not expect, and only
# the first of those is yours to change. Never delete a value simply because it
# looks untidy. It changes your results, and nobody reading them would know.

 

8. Histograms are the standard way of looking at the distribution of a continuous variable (see Section 4.2.2). Create histograms for bmi and alcohol and put them side by side in a single figure, by splitting your plotting device into 1 row and 2 columns with par(mfrow = c(1, 2)). The two distributions look quite different - how would you describe them?

One thing to be careful of is that a histogram can look quite different depending on the number of ‘breaks’ (bins) used. The hist() function chooses these for you, but they’re only a suggestion, so it’s always worth trying a few values of the breaks argument before you settle on one (see ?hist).

par(mfrow = c(1, 2))
hist(cardiac$bmi, main = "", xlab = "bmi")
hist(cardiac$alcohol, main = "", xlab = "alcohol (units/week)")

# bmi is roughly symmetric, with most patients in the middle and a modest tail
# to the right. alcohol looks quite different: most patients drink little or
# nothing, and a small number drink a great deal, which gives a long tail
# stretching out to 82 units. You'll come back to alcohol in Q9.

 

9. Have another look at the histogram of alcohol you have just drawn. A small number of patients drink a great deal more than everybody else, and those few stretch the distribution out into a long tail to the right. Data like this are described as right skewed, and they are very common in health data. It’s worth doing something about, because a lot of what you might want to do with a variable later on, from summarising it to modelling it, works better when the values are more evenly spread.

One way of dealing with this is to transform the variable. alcohol is a count, the number of units drunk in a week, and the square root is the usual choice for count data. It also copes with the patients who drank nothing, because sqrt(0) is simply 0. Notice that this is a different choice from Exercise 4, Q6, where you took logs of triglyceride. A blood concentration and a count of units are different sorts of measurement and they don’t call for the same transformation.

Create a new variable in the cardiac dataframe holding the square root of alcohol, then plot its histogram next to the untransformed one. Looking at the two, has the tail been pulled in?

cardiac$alcohol_sqrt <- sqrt(cardiac$alcohol)

par(mfrow = c(1, 2))
hist(cardiac$alcohol, main = "untransformed", xlab = "alcohol (units/week)")
hist(cardiac$alcohol_sqrt, main = "square root", xlab = "sqrt(alcohol)")

# Yes. The heavy drinkers are no longer strung out along a long tail, the
# values are spread much more evenly across the range, and all 163 patients
# are still in the plot.

# Why not a log, as you used in Exercise 4? Try it and see what happens:
# hist(log(cardiac$alcohol))
# 58 of these patients drank nothing at all and log(0) is -Inf, so the plot you
# get is built from 105 patients rather than 163. R doesn't warn you, it
# doesn't produce an error, and the histogram looks perfectly reasonable.
# Triglyceride didn't have this problem, because its single zero had already
# been set to NA back in Exercise 4 Q1.

 

10. Scatterplots are great for visualising relationships between two continuous variables (Section 4.2.1). Before you draw one, decide which variable belongs on which axis. By convention the explanatory variable goes on the x axis and the response goes on the y axis, so have a think about which is which before you draw anything.

Start with bmi and systolic blood pressure. Which is the explanatory variable, and which the response? Plot them the right way round. Now do the same for hdlchol and triglyceride. Is the choice as easy this time?

par(mfrow = c(1, 2))

# Easy one. Carrying more weight raises blood pressure, not the other way round,
# so bmi is explanatory and goes on x. The relationship is real but weak, which
# is normal.
plot(cardiac$bmi, cardiac$systolic,
     xlab = "bmi", ylab = "systolic (mmHg)")

# Harder one. Neither clearly causes the other; both are markers of the same
# underlying metabolic state. If you have to choose, a raised triglyceride is
# usually taken to drive HDL down rather than the reverse, so triglyceride goes
# on x. The relationship is negative.
plot(cardiac$triglyceride, cardiac$hdlchol,
     xlab = "triglyceride (mmol/l)", ylab = "HDL cholesterol (mmol/l)")

 

11. When visualising differences in a continuous variable between levels of a factor (categorical variable) then a boxplot is your friend (avoid using bar plots - Google ‘bar plots are evil’ for more info). Create a boxplot to visualise the differences in HDL cholesterol at each level of smoking status (don’t forget to use the recoded version of this variable you created in Q4). Include x and y axis labels in your plot. Make sure you understand the anatomy of a boxplot before moving on - please ask if you’re not sure (also see Section 4.2.3 of the book).

# note: Fsmoking is the recoded smoking variable created in Q4
boxplot(hdlchol ~ Fsmoking, data = cardiac,
        xlab = "smoking status", ylab = "HDL cholesterol (mmol/l)")

# the patients who have never smoked have a higher HDL cholesterol than either
# the current smokers or the ex-smokers. Their median is 1.60, against 1.31 for
# the current smokers and 1.21 for the ex-smokers, and it sits above the upper
# quartile of both of the other groups.

 

12. An alternative to the boxplot is the violin plot, which combines a boxplot with a kernel density plot and shows you the shape of the distribution rather than just its quartiles. You will first need to install the vioplot package from CRAN and make it available with library(vioplot). The vioplot() function then works in much the same way as boxplot(). Draw the HDL comparison from Q11 again as a violin plot and compare the two.

Every plot so far has appeared in the RStudio plot pane, which is fine while you’re exploring but no use at all when you need the figure in a document. Getting a plot out of R takes three steps. You open a graphics device that is a file rather than a window, you draw the plot exactly as you have been doing, and then you close the device with dev.off(). Nothing appears on screen while you do it, and if you forget the dev.off() your file will be unreadable (see Section 4.5 of the book). Save your violin plot as a pdf in the output directory you created in Exercise 1, then find the file and open it outside RStudio to check it really worked. Exercise 6 takes this further, including how to get a figure into Word or PowerPoint at a resolution that doesn’t embarrass you.

# violin plot. install.packages("vioplot") first if you have not already
library(vioplot)
vioplot(hdlchol ~ Fsmoking, data = cardiac, xlab = "smoking status",
        ylab = "HDL cholesterol (mmol/l)", col = "lightblue")

# the boxplot gives you the quartiles, the violin gives you the shape as well.
# Here they agree, which is reassuring rather than dull: it means the medians
# are not being propped up by one odd cluster of patients.

# now the same plot again, sent to a file instead of to the screen. Sizes are
# in inches. Nothing appears in the plot pane while these three lines run, and
# without the dev.off() the file would be left open and unreadable.
pdf('output/ex5_hdl_violin.pdf', width = 7, height = 5)
vioplot(hdlchol ~ Fsmoking, data = cardiac, xlab = "smoking status",
        ylab = "HDL cholesterol (mmol/l)", col = "lightblue")
dev.off()

# your output directory should now contain ex5_hdl_violin.pdf. pdf is a vector
# format, so it stays perfectly sharp however far you enlarge it.

 

13. To explore the relationships between several continuous variables at once it’s hard to beat a pairs plot. Create a pairs plot for the variables; age, systolic, diastolic, tchol, hdlchol, triglyceride and bmi (see Section 4.2.5 of the book for more details). If it looks a little cramped in RStudio then click on the ‘zoom’ button in the plot viewer to see a larger version.

These take a little practice to read, so it’s worth going slowly the first time. The variable names run down the diagonal and every other panel is a scatterplot of two of your variables. To work out which two, look at the row and the column the panel sits in: the variable named in that row is on the y axis and the variable named in that column is on the x axis. This also means that the panels above the diagonal show exactly the same pairs of variables as the panels below it, just with the axes swapped over, so you only need to look at one half. Which pair of variables looks most strongly related to you, and which variable seems to be related to almost nothing?

The pairs() function is very flexible and you can change what is drawn in each panel, adding things like smoothers or correlation coefficients. Have a look at Section 4.2.5 of the book and the examples in ?pairs if you would like to try this.

plot_vars <- c("age", "systolic", "diastolic", "tchol", "hdlchol",
               "triglyceride", "bmi")

pairs(cardiac[, plot_vars])

# systolic and diastolic are the most strongly related pair, which is no
# surprise given that they are the same measurement taken at two points in the
# heartbeat. hdlchol against triglyceride is the next most obvious, sloping the
# other way.

# age is related to almost nothing here, and that's worth thinking about.
# Everyone in this study is between 55 and 75, so there isn't much spread in
# age for a relationship to show itself in. A variable can look unimportant
# just because of who was recruited.

 

End of Exercise 5