TECH3100 — Data Visualisation in R

Week 3
Visualising Numerical Variables

Histograms · Cumulative relative frequency · Boxplots
Every code block on these slides has been run in R 4.3.3 and uses only datasets that ship with base R — nothing to download.
navigate T contents Esc close
1

1. Orientation

Where this week sits, and what you will be able to do by the end of it

1.1

1.1 Where Week 3 sits

Week 1 gave you chart types and categorical variables. Week 2 gave you populations, samples and the descriptive statistics that summarise discrete data. This week we handle continuous numerical variables, where every observation is effectively unique and counting distinct values no longer works.

WeekTopic
1Types of variable. Visualisation for categorical variables.
2Population and sample. Descriptive statistics. Visualisation for discrete.
3Visualisation for continuous: histogram, cumulative relative frequency, boxplots
4Pictograms, scatter plots, correlation
5Group presentations / linear trend line
6–7Time series; multivariate data
1.2

1.2 What you will be able to do

By the end of this lecture and the accompanying notebook you should be able to:

  1. Produce a stem-and-leaf plot and say what it preserves that a histogram discards.
  2. Compute and interpret the mean, median, range, quantiles, IQR, variance and standard deviation in R, and explain which measure is appropriate for a given shape.
  3. Build frequency and density histograms, and explain why unequal bin widths force you to use density.
  4. Plot and read a cumulative relative frequency curve, including reading quantiles directly off it.
  5. Construct boxplots, apply the 1.5 × IQR rule, and compare groups side by side.
  6. Recognise when a numerical summary is hiding something a plot would reveal.
The through-line for the week: a summary statistic compresses a distribution down to one number. Compression always loses information. Visualisation is how you check what was lost.
1.3

1.3 The datasets we will use

Everything in this lecture uses data that is already installed with R. Type the name and it is there — no files, no working directory, no import errors.

ObjectWhat it isSizeWhy we use it
airqualityDaily air measurements, New York, 1973153 rowsRight-skewed ozone; contains missing values
faithfulOld Faithful geyser eruptions272 rowsStrongly two-peaked — breaks naive summaries
riversLengths of major North American rivers141 valuesExtreme right tail; motivates log scales
precipAnnual rainfall, 70 US cities70 valuesMild left skew
chickwtsChick weight by feed supplement71 rowsSix groups — side-by-side boxplots
# Nothing to install, nothing to load.
str(airquality)
head(faithful)
?airquality        # every built-in dataset has a help page
1.4

1.4 Discrete vs continuous: why the tools change

In Week 2 you visualised discrete data with bar charts, because the variable took a manageable number of distinct values and you could count how often each one occurred.

A continuous variable does not work that way. Measure fuel use, ozone concentration or waiting time finely enough and almost every observation is unique, so every "bar" has a count of one. A bar chart of continuous data is just a picture of your measurement precision.

The fix
Stop counting values. Start counting intervals. Divide the number line into bins, count how many observations fall in each, and plot those counts. That is a histogram, and it is the foundation for everything else this week.
Check it yourself

In R, length(unique(airquality$Ozone)) returns 68 distinct values across 116 measurements. A bar chart would need 68 bars, most of them height 1 or 2. A histogram needs about 9.

2

2. Keeping the Data Visible

Stem-and-leaf plots: a histogram you can read the numbers off

2.1

2.1 The stem-and-leaf idea

A stem-and-leaf plot splits every number into two parts. The leading digits become the stem; the final digit becomes the leaf. Leaves are then listed against their stem.

Example: the value 78

Stem = 7, leaf = 8. Written as 7 | 8. Add 72, 75 and 79 and the row becomes 7 | 2 5 8 9.

Turn the plot on its side and the rows form bars — it is a histogram whose bin widths are powers of ten. The difference is that no information is thrown away: you can read every original observation back out of the plot.

This is why stem-and-leaf is a good first look at a small dataset. It shows the shape and preserves the values. It stops being practical above a few hundred observations, at which point the leaves become an unreadable wall of digits.
2.2

2.2 stem() in R

Base R provides stem(). It prints to the console rather than drawing a graphic, so it is text you can copy into a report.

stem(airquality$Temp)
  The decimal point is at the |

  56 | 0000
  58 | 0000
  60 | 000
  62 | 000
  64 | 0000
  66 | 0000000
  68 | 0000000
  70 | 0000
  72 | 00000000
  74 | 00000000
  76 | 0000000000000000
  78 | 000000000000
  80 | 0000000000000000
  82 | 0000000000000
  84 | 0000000000
  86 | 000000000000
  88 | 00000
  90 | 00000
  92 | 00000000
  94 | 00
  96 | 00

Temperature is recorded in whole degrees Fahrenheit, so every leaf is 0. R has chosen stems two units wide: the row 76 holds both 76° and 77°.

2.3

2.3 Reading the plot

Read the shape from the lengths of the rows, exactly as you would read bar lengths.

What it tells you

  • Centre. The longest rows sit around 76–86°F.
  • Spread. Values run from 56° to 97°.
  • Shape. The tail on the low side is longer and thinner than the high side — a mild left skew.
  • Granularity. Every leaf is 0, telling you the data were rounded to whole degrees.

Controlling the stems

The scale argument stretches or compresses the plot. Larger values give more stems and finer detail.

stem(airquality$Temp, scale = 0.5)  # fewer, wider stems
stem(airquality$Temp, scale = 2)    # more, narrower stems

Try both. The choice of stem width has exactly the same effect on apparent shape as the choice of bin width in a histogram — a point we return to in Section 5.

2.Q

Knowledge check — Section 2

A stem-and-leaf row reads 4 | 2 2 5 8, with the decimal point at the |. Which values does it represent?
The stem gives the leading digits and each leaf gives one final digit, so the row holds four observations: 42, 42, 45 and 48. Because the individual values are recoverable, a stem-and-leaf plot loses no information — unlike a histogram, which only records how many fell in each bin.
Why is stem() a poor choice for a variable with 50,000 observations?
There is no computational barrier — the problem is perceptual. With 50,000 leaves the rows wrap across the console and you lose the visual comparison of row lengths that made the plot useful. Once the data are too numerous to display individually, binning them into a histogram is the sensible trade.
3

3. Summarising the Centre

Mean, median, and the question of which one to trust

3.1

3.1 Population and sample notation

Two sets of symbols describe the same quantities, depending on whether you have measured everyone or only a sample. Getting them straight now saves confusion later.

PopulationSample
Size (may be infinite)
Mean
Variance
Standard deviation
Note the one structural difference: the sample variance divides by , not . Section 4.8 explains why. R's var() and sd() always use the sample formula.
3.2

3.2 Mean and median

Mean

The balance point of the data. Every observation contributes, so every observation can move it.

Median
The middle value once the data are sorted.

Depends only on the order of the observations, not their magnitudes. Half the data lie below it.

Finding the median by hand

Sort the data first, then:

x <- c(31, 34, 35, 36, 38, 40, 41)
mean(x)      # 36.42857
median(x)    # 36
3.3

3.3 One outlier, two very different answers

Take seven observations and change only the largest one. The mean moves by more than fifty units; the median does not move at all.

DataMeanMedian
31, 34, 35, 36, 38, 40, 4136.4336
31, 34, 35, 36, 38, 40, 41089.1436
a <- c(31, 34, 35, 36, 38, 40, 41)
b <- c(31, 34, 35, 36, 38, 40, 410)

mean(a);   median(a)     # 36.42857   36
mean(b);   median(b)     # 89.14286   36
The mean of b is larger than six of the seven observations. As a description of "a typical value" it has failed completely. This is what statisticians mean by calling the median robust and the mean sensitive.

Neither is wrong. If you are budgeting total fuel, insurance payouts or total revenue, you need the mean, because the mean multiplied by gives the total. If you are describing a typical case, the median is usually the honest choice.

3.4

3.4 The same effect in real data

Daily ozone in airquality is strongly right-skewed: most days are low, a handful are very high. The mean sits well above the median as a result.

oz <- airquality$Ozone[!is.na(airquality$Ozone)]

length(oz)    # 116
mean(oz)      # 42.12931
median(oz)    # 31.5
Ozone concentration, 116 days 0 40 80 120 160 0 9 18 28 37 Ozone (ppb) Frequency
Figure 3.1: The long right tail drags the mean above the median.

The mean of 42.1 exceeds the median of 31.5 by a third. That gap is the skew, expressed numerically. For a symmetric distribution the two would nearly coincide.

3.5

3.5 summary() and the missing-value trap

summary() gives five numbers plus the mean in one call — and, crucially, tells you how many values are missing.

summary(airquality$Ozone)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's
   1.00   18.00   31.50   42.13   63.25  168.00      37

Thirty-seven of the 153 days have no ozone reading. Most R summary functions refuse to guess what to do about that:

mean(airquality$Ozone)                 # NA
mean(airquality$Ozone, na.rm = TRUE)   # 42.12931
Returning NA is deliberate, not a bug. R is telling you that the answer depends on a decision you have not made. Use na.rm = TRUE only once you have checked why the values are missing — if the monitor failed on the hottest days, dropping them will bias every statistic you compute.
3.Q

Knowledge check — Section 3

A dataset of 200 house sale prices has mean $940,000 and median $610,000. What does this most likely indicate?
A mean well above the median is the signature of right skew: a few large values inflate the mean while leaving the median untouched. The last option is precisely backwards — the median is $610,000, so half the houses sold below that, and far fewer than half exceeded $940,000.
mean(airquality$Ozone) returns NA. What is R communicating?
Arithmetic involving an unknown quantity yields an unknown result, so the propagating NA is mathematically correct behaviour. Adding na.rm = TRUE instructs R to compute the mean of the values that are present — a defensible choice, but one you should make consciously after considering why the data are missing.
4

4. Summarising the Spread

Range, quantiles, IQR, variance and standard deviation

4.1

4.1 Range, and why it is fragile

The range is the distance from the smallest observation to the largest. It is the simplest measure of spread and the least reliable.

oz <- airquality$Ozone[!is.na(airquality$Ozone)]

range(oz)               # 1 168   <- returns min and max, NOT the difference
max(oz) - min(oz)       # 167     <- the range as a single number
A common trap: R's range() returns the two endpoints, not the distance between them. If you want one number, subtract.

The range depends on exactly two observations — the two most extreme, which are also the two most likely to be errors, and the two whose values are least reproducible if you collected the data again. Every other observation is ignored.

We need a measure of spread that uses the middle of the data instead. That is what quantiles give us.

4.2

4.2 Quantiles

A quantile is a cut point that divides sorted data into groups of specified proportion. The p-th quantile is the value below which a proportion p of the data lies.

Proportion belowPercentage belowName
0.2525%Lower quartile, Q1
0.5050%Median, Q2
0.7575%Upper quartile, Q3

Quartiles split the data into four equal parts; deciles into ten; percentiles into a hundred. They are all the same idea at different resolutions.

25% 25% 25% 25% Q1 Q2 (median) Q3 IQR = Q3 − Q1 (central half of the data) min max
Figure 4.1: Quartiles divide the data into four equal-sized groups.
4.3

4.3 The interquartile range

The interquartile range is the width of the middle half of the data:

Unlike the range, it is computed from the bulk of the data and is completely unaffected by however extreme the most extreme observations happen to be. Move the largest value from 168 to 168,000 and the IQR does not change.

oz <- airquality$Ozone[!is.na(airquality$Ozone)]

quantile(oz, 0.25)      # 18.00
quantile(oz, 0.75)      # 63.25
IQR(oz)                 # 45.25

# equivalently
quantile(oz, 0.75) - quantile(oz, 0.25)

So the central half of the ozone readings spans 45.25 ppb, from 18 to 63.25. That is a far more useful description of typical variability than the range of 167, which is driven entirely by one exceptional day.

4.4

4.4 How R actually computes a quantile

When does not land exactly on an observation, R interpolates between the two neighbouring values. Its default method computes a position

then takes the -th value in the sorted data, interpolating linearly when is not a whole number.

Worked example

Sorted data: 3, 7, 8, 12, 15, 21, 24, 30, so . Find .

  1. lies between positions 2 and 3, i.e. between the values 7 and 8.
  2. Go 0.75 of the way from 7 to 8: 
x <- c(3, 7, 8, 12, 15, 21, 24, 30)
quantile(x, 0.25)       # 7.75  -- matches the hand calculation

Nine different quantile definitions exist and textbooks disagree about which to use; some define the position as instead, giving 7.25 here. R implements all nine via the type argument. Small differences between software packages usually trace back to this choice, not to an error.

4.5

4.5 Quantiles in R

Called with no probabilities, quantile() returns the five-number summary. Pass a vector of probabilities to get exactly the cut points you want.

oz <- airquality$Ozone[!is.na(airquality$Ozone)]

quantile(oz)
    0%    25%    50%    75%   100%
  1.00  18.00  31.50  63.25 168.00
# any probabilities you like
quantile(oz, c(0.10, 0.90))
 10%  90%
 7.5 87.0

So 10% of days had ozone below 7.5 ppb and 10% were above 87 ppb. Note quantile() has no na.rm default — pass na.rm = TRUE if your vector still contains missing values.

4.6

4.6 Variance, step by step

Spread means distance from the centre. The obvious measure — average deviation from the mean — is useless, because the deviations always sum to exactly zero. Squaring them first solves that.

Take , so and .

24445579Sum
−3−1−1−100240
91110041632
The deviations summing to zero is not a coincidence of this example — it is true of every dataset, and it is exactly why we square before averaging.
4.7

4.7 var() and sd()

Confirm the hand calculation, then use the built-in functions from here on.

x <- c(2, 4, 4, 4, 5, 5, 7, 9)
n <- length(x)

# the long way
d  <- x - mean(x)          # deviations
sum(d)                     # 0        <- always
sum(d^2)                   # 32
sum(d^2) / (n - 1)         # 4.571429

# the short way
var(x)                     # 4.571429
sd(x)                      # 2.13809
sqrt(var(x))               # 2.13809  <- sd is just the square root of the variance
Units matter
If is in ppb, the variance is in ppb2 — a unit with no physical meaning. Taking the square root returns the standard deviation to the original units, which is why sd is what gets reported.
Recipe

Mean-correct → square → sum → divide by → that is the variance → square root → that is the standard deviation.

4.8

4.8 Why divide by ?

Four reasons, in increasing order of depth:

  1. It is the standard convention. Every downstream statistical procedure — confidence intervals, t-tests, regression — assumes the divisor was .
  2. Software agrees. R's var() and sd() use . Excel's VAR.S does too, though VAR.P does not — a classic source of mismatched answers between the two.
  3. It behaves sensibly at . One observation tells you nothing about variability, and correctly yields an undefined answer rather than a falsely confident zero.
  4. It is unbiased. Imagine drawing sample after sample from a population. Dividing by gives variances that are, on average, slightly too small, because the sample mean sits closer to your own sample than the true does. Dividing by corrects exactly this, so the long-run average of equals .
Point 4 is the real reason. The other three are consequences of it. Note also that the correction shrinks as grows: at the difference between dividing by 499 and 500 is under a quarter of one percent.
4.9

4.9 Interpreting the standard deviation

The standard deviation is in the same units as the data, which makes it directly interpretable as a typical distance from the mean. For data with a roughly bell-shaped distribution, three benchmarks are worth memorising.

-3s -2s -1s mean +1s +2s +3s 68% 95% 99.7%
Figure 4.2: For an approximately bell-shaped distribution, roughly 68% of observations fall within one standard deviation of the mean, 95% within two, and 99.7% within three.
These percentages are a property of the bell shape, not of the standard deviation itself. Applied to a distribution that is strongly skewed or two-peaked, they can be badly wrong — as the next slide shows.
4.10

4.10 Does the rule survive contact with real data?

Here is the proportion of observations actually falling within one standard deviation of the mean, for five built-in datasets. The bell-curve prediction is 68%.

DatasetnMeansdWithin 1 sdShape
airquality$Temp15377.889.4766.7%Roughly symmetric
precip7034.8913.7168.6%Mild left skew
faithful$waiting27270.9013.5962.9%Two peaks
airquality$Ozone11642.1332.9972.4%Right skew
rivers141591.18493.8790.1%Extreme right skew
v <- rivers
mean(abs(v - mean(v)) <= sd(v))     # 0.9007092

The rule works well for the two datasets closest to symmetric and fails badly for rivers, where a handful of enormous values inflate the standard deviation so much that 90% of rivers fall inside one sd of the mean.

Treat 68/95/99.7 as a sanity check, not a fact. If it is badly violated, that is itself informative: your distribution is not bell-shaped, and you should plot it before summarising it further.
4.11

4.11 Standard deviation or IQR?

Both measure spread. They answer slightly different questions and fail in different circumstances.

Standard deviationInterquartile range
UsesEvery observationThe middle half only
OutliersHighly sensitiveUnaffected
Pairs withThe meanThe median
CoversAbout the central 2/3 (within ±1 sd)Exactly the central 1/2
Best whenRoughly symmetric, no extreme valuesSkewed, or outliers present

Because sd spans about two-thirds of the data while the IQR spans exactly one half, you should normally expect to exceed the IQR. It does for all five datasets on the previous slide.

Report them in matched pairs: mean with sd, or median with IQR. Quoting a median alongside a standard deviation signals that the choice was not thought through.
4.12

4.12 Shifting and scaling

Changing units is a linear transformation, and its effect on each statistic is predictable.

TransformationMeanVarianceStandard deviation
Add a constant unchangedunchanged
Multiply by a constant

Shifting slides the whole distribution along without stretching it, so spread is untouched. Scaling stretches it, and because variance is in squared units it scales by .

tempF <- airquality$Temp
tempC <- (tempF - 32) * 5/9        # shift by -32, then scale by 5/9

mean(tempF)   # 77.88235      mean(tempC)   # 25.49020
sd(tempF)     #  9.46527      sd(tempC)     #  5.25848
var(tempF)    # 89.59133      var(tempC)    # 27.65165

sd(tempF) * 5/9         # 5.25848   <- matches sd(tempC)
var(tempF) * (5/9)^2    # 27.65165  <- matches var(tempC)

The −32 shift changed the mean but had no effect at all on the sd or the variance. Only the ×5/9 scaling did.

4.Q

Knowledge check — Section 4

Every salary in a dataset receives a flat $5,000 increase. What happens to the mean and the standard deviation?
A flat increase shifts the entire distribution without altering the gaps between observations, and spread is entirely a matter of those gaps. Had every salary instead been multiplied by 1.05, the mean and the sd would both have risen by 5% — and the variance by 5% squared.
For rivers, 90.1% of values lie within one standard deviation of the mean rather than the expected 68%. What does this indicate?
The 68/95/99.7 percentages are properties of the bell shape, and rivers has an extreme right tail. A few very long rivers inflate the standard deviation so much that the interval mean ± 1 sd becomes wide enough to swallow 90% of the data. The correct response is to report the median and IQR instead.
Which pairing of statistics is appropriate for strongly right-skewed data?
Skew and outliers distort both the mean and the standard deviation, while the median and IQR depend only on the middle of the sorted data and are unaffected. Keeping the pairing consistent matters: the median describes the centre robustly, and the IQR describes the spread on the same terms.
5

5. Histograms

Binning continuous data, and the frequency-versus-density decision

5.1

5.1 What a histogram does

A histogram divides the range of the data into intervals called bins, counts the observations in each, and draws a bar for each bin. Bars touch, because the underlying variable is continuous and the bins are adjacent intervals rather than separate categories.

What you read off it
  • Where the bulk of the data sits
  • How wide the spread is
  • Whether it is symmetric or skewed
  • How many peaks there are
  • Whether values sit detached from the rest
What you lose

The individual values. Once observations are pooled into a bin, the histogram records only how many landed there — a genuine trade of detail for legibility, and the reason stem-and-leaf still has a place.

A bar chart and a histogram look similar and mean different things. A bar chart's horizontal axis lists categories in whatever order you choose; a histogram's is a number line, so bar order and bar width both carry meaning.

5.2

5.2 hist() in R

oz <- airquality$Ozone[!is.na(airquality$Ozone)]

hist(oz,
     xlab = "Ozone (ppb)",
     main = "Ozone concentration, 116 days")
Ozone concentration, 116 days 0 20 40 60 80 100 120 140 160 180 0 9 18 28 37 Ozone (ppb) Frequency

Always label the axes and give the plot a title. xlab, ylab and main take character strings; the default labels are the R expression you passed in, which is meaningless to anyone reading your report.

5.3

5.3 Frequency and density

hist() can plot two different things on the vertical axis.

freq = TRUE (default)
Bar height = count of observations in the bin.

Easy to explain. Heights are whole numbers you can read directly.

freq = FALSE
Bar height = density:

Heights are not counts. Instead, area equals proportion, and the total area is exactly 1.

hist(oz, freq = FALSE, xlab = "Ozone (ppb)", main = "")

# verify the total area is 1
h <- hist(oz, plot = FALSE)
sum(h$density * diff(h$breaks))     # 1
With equal bin widths the two plots have identical shape — density is just frequency rescaled by a constant, so only the axis numbers change. With unequal widths they are completely different plots, and only one of them is honest.
5.4

5.4 Unequal bins: frequency misleads

Suppose we bin ozone finely at the low end and coarsely at the high end. The 50–100 bin is five times wider than its neighbours, so it accumulates a large count purely by being wide.

brk <- c(0, 10, 20, 30, 40, 50, 100, 200)

par(mfrow = c(1, 2))
hist(oz, breaks = brk, freq = TRUE,  xlab = "Ozone", main = "(a) frequency")
hist(oz, breaks = brk, freq = FALSE, xlab = "Ozone", main = "(b) density")
(a) freq = TRUE — misleading 0 50 100 150 200 0 7 14 20 27 Ozone (ppb) Frequency
(b) freq = FALSE — honest 0 50 100 150 200 0.000 0.006 0.011 0.017 0.022 Ozone (ppb) Density

In panel (a) the highlighted 50–100 bin looks like the second-largest group in the dataset. In panel (b) the same bin is one of the shortest, because its 27 observations are spread thinly across a 50-unit interval.

Panel (b) is correct. If bin widths are unequal you must plot density, because the eye compares bar areas, and only density makes area proportional to the number of observations.
5.5

5.5 Choosing the number of bins

The breaks argument controls bin count. R treats a single number as a suggestion and rounds to convenient boundaries.

par(mfrow = c(1, 3))
hist(faithful$waiting, breaks = 5,  main = "too few")
hist(faithful$waiting, breaks = 12, main = "about right")
hist(faithful$waiting, breaks = 60, main = "too many")
breaks = 5 40 60 80 100 0 20 40 61 81 waiting (min) Frequency
breaks = 12 40 60 80 100 0 14 28 41 55 waiting (min) Frequency

With five bins the two peaks are smeared into one broad lump. With twelve they are unmistakable. With sixty, random noise in individual bins starts to look like structure.

Bin width is a genuine analytical choice, not a cosmetic one. Always try several before concluding anything about shape — if a feature disappears when you change the bin width slightly, it was probably never there.
5.6

5.6 The vocabulary of shape

Four descriptions cover most of what you will need to say about a distribution.

Roughly symmetric — airquality$Temp 55 70 85 100 0 8 17 26 34 Temp (°F) Freq
Right-skewed — rivers 0 1000 2000 3000 4000 0 21 42 63 84 length (miles) Freq
Left-skewed — precip 0 20 40 60 0 6 12 19 25 rainfall (in) Freq
Bimodal — faithful$waiting 40 60 80 100 0 14 28 41 55 waiting (min) Freq

Skew is named for the direction of the tail, not the location of the peak. rivers is right-skewed because its long thin tail extends to the right, even though its tallest bar is on the left.

5.7

5.7 When the summary hides the shape

faithful$waiting records the gap in minutes between eruptions of the Old Faithful geyser. Its numerical summary looks entirely unremarkable.

summary(faithful$waiting)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
   43.0    58.0    76.0    70.9    82.0    96.0
Old Faithful: waiting time between eruptions, n = 272 40 50 60 70 80 90 100 0 14 28 41 55 Waiting time (minutes) Frequency
Figure 5.1: Two clearly separated groups. The mean of 70.9 falls in the gap between them.
The mean waiting time is 70.9 minutes — and 70.9 minutes is one of the least likely waits to observe. The data contain two distinct eruption regimes, and no single-number summary of centre can represent them. Only the plot reveals this.
5.8

5.8 Heavy tails and the log transform

When one tail is extreme, a histogram on the original scale becomes a single tall bar next to a strip of near-empty space. Plotting the logarithm of the values spreads them out.

par(mfrow = c(1, 2))
hist(rivers,        xlab = "length (miles)",       main = "original scale")
hist(log10(rivers), xlab = "log10(length)",         main = "log scale")
Original scale 0 1000 2000 3000 4000 0 21 42 63 84 length (miles) Frequency
Log scale 2 2.4 2.8 3.2 3.6 0 12 25 38 50 log10(length) Frequency

On the log scale the distribution is close to symmetric, and structure that was compressed into the first bar becomes visible. This is why financial returns, city populations, incomes and file sizes are so often plotted on log axes.

The transformed values are logarithms, so a bar at 2.6 means miles. Label such axes carefully or your reader will misread them by orders of magnitude.

5.Q

Knowledge check — Section 5

A histogram uses bins of unequal width. Why must the vertical axis show density rather than frequency?
The eye judges a bar by its area, and with unequal widths only density makes area proportional to the number of observations. In the ozone example the 50–100 bin held 27 observations and looked like the second-largest group on the frequency axis, yet on the density axis it was one of the shortest — because those 27 values were spread across an interval five times wider than its neighbours.
faithful$waiting has mean 70.9 and median 76.0. Why is neither an adequate summary?
The distribution is bimodal: waits cluster near 55 minutes and near 80 minutes, with relatively few in between. The mean of 70.9 falls squarely in the sparse gap, making it one of the least likely waits to actually observe. This is the strongest argument in the whole lecture for plotting before summarising.
A histogram's tallest bar is at the far left and a thin tail extends to the right. How is this described?
Skew is named for the direction in which the tail extends, not where the peak sits — a point that catches people out regularly. Here the tail runs right, so the distribution is right-skewed, and you should expect the mean to sit above the median.
6

6. Cumulative Relative Frequency

Reading proportions and quantiles straight off a curve

6.1

6.1 The idea

Instead of asking how many observations fall in an interval, ask how many fall at or below a value. Plot that proportion against the value and you get the empirical cumulative distribution function.

The curve starts at 0 on the left, ends at 1 on the right, and never decreases. It steps up by at every observation, which is why it looks like a staircase.

Advantages over a histogram
  • No bin width to choose — nothing is arbitrary
  • No information is discarded
  • Any quantile can be read straight off it
Disadvantage

Shape is much harder to see. Peaks become steep sections and gaps become flat sections, which takes practice to read. Most analysts use both.

6.2

6.2 ecdf() in R

oz <- airquality$Ozone[!is.na(airquality$Ozone)]

plot(ecdf(oz),
     verticals = TRUE,
     main = "Cumulative relative frequency",
     xlab = "Ozone (x, ppb)",
     ylab = "Proportion less than or equal to x",
     ylim = c(0, 1))
Cumulative relative frequency 0 0.25 0.5 0.75 1 0 30 60 90 120 150 180 Ozone (ppb), x Proportion ≤ x
Figure 6.1: Each step up corresponds to one observation.

ecdf() returns a function, so you can also evaluate it directly rather than only plotting it:

F <- ecdf(oz)
F(50)     # 0.7068966  -> about 71% of days were at or below 50 ppb
F(100)    # 0.9396552  -> about 94% were at or below 100 ppb
6.3

6.3 Reading quantiles off the curve

The ECDF makes quantiles geometric. To find the median, start at 0.5 on the vertical axis, travel right to the curve, then drop to the horizontal axis.

0 0.25 0.5 0.75 1 Q1 = 18 median = 31.5 Q3 = 63.25 0 30 60 90 120 150 180 Ozone (ppb), x Proportion ≤ x
Figure 6.2: Construction lines at 0.25, 0.50 and 0.75 locate the three quartiles.
plot(ecdf(oz), verticals = TRUE, main = "", xlab = "Ozone (ppb)",
     ylab = "Proportion <= x")

q <- quantile(oz, c(0.25, 0.5, 0.75))
abline(h = c(0.25, 0.5, 0.75), lty = 2, col = "red")   # horizontal guides
abline(v = q,                  lty = 2, col = "red")   # vertical guides

The steepness of the curve is itself informative: the sharp rise between 10 and 40 ppb tells you most days are concentrated there, while the near-flat stretch beyond 100 tells you high-ozone days are rare.

6.4

6.4 Two plots in one figure

par(mfrow = c(rows, cols)) splits the plotting area into a grid, and subsequent plots fill it row by row. Stacking a histogram above its ECDF lets you match features between the two.

par(mfrow = c(2, 1))                       # 2 rows, 1 column

hist(oz, freq = FALSE, xlim = c(0, 180),
     xlab = "", ylab = "Density", main = "Ozone: density and cumulative views")

plot(ecdf(oz), verticals = TRUE, xlim = c(0, 180),
     xlab = "Ozone (x, ppb)", ylab = "Proportion <= x", main = "")

par(mfrow = c(1, 1))                       # reset when finished
Two habits worth forming: set matching xlim on both panels so the axes align and features line up vertically, and reset par(mfrow = c(1, 1)) afterwards — the setting persists and will silently split your next plot too.

Where the histogram has a tall bar, the ECDF is steep. Where the histogram is flat, the ECDF is nearly horizontal. They are two views of the same information.

6.Q

Knowledge check — Section 6

On an ECDF, what does a long horizontal (flat) stretch indicate?
The curve rises by 1/n at each observation, so it can only stay flat where there are no observations to step it up. A flat stretch therefore marks a gap in the data — the same feature that appears as an empty region between bars on a histogram.
Using F <- ecdf(oz), the call F(50) returns 0.7069. What does this mean?
The ECDF maps a value to the proportion of the sample at or below it, so 50 ppb sits at roughly the 71st percentile of these 116 days. The first option inverts the relationship — that is what quantile(oz, 0.5) would answer, and it returns 31.5.
7

7. Boxplots

Five numbers, one picture, many groups at once

7.1

7.1 The five-number summary

A boxplot condenses a distribution into five numbers, then draws them.

ElementWhat it shows
Lower whisker endSmallest observation still inside the lower fence
Bottom of boxLower quartile, Q1
Line inside boxMedian
Top of boxUpper quartile, Q3
Upper whisker endLargest observation still inside the upper fence
Individual pointsObservations beyond the fences, plotted one by one

The box therefore spans the IQR and contains the central half of the data. The position of the median line within the box indicates skew: pushed towards the bottom means a longer upper tail.

7.2

7.2 Anatomy of a boxplot

Ozone upper whisker = 122 Q3 = 63.5 median = 31.5 Q1 = 18 lower whisker = 1 outliers plotted individually IQR 0 36 72 108 144 180 Ozone (ppb)
Figure 7.1: airquality$Ozone. The box holds the middle 50% of the 116 readings.

Reading this plot: the median sits well below the centre of the box, and the upper whisker is much longer than the lower one. Both features say the same thing — the distribution is right-skewed, exactly as the histogram in Section 3 showed.

7.3

7.3 The 1.5 × IQR rule

Whiskers do not simply run to the minimum and maximum. R first computes two fences:

Each whisker then extends only as far as the most extreme observation still inside its fence. Anything beyond is drawn as an individual point.

Worked on the ozone data

, , so and .

boxplot.stats(oz)$out     # 135 168
A point beyond the fence is flagged, not condemned. The 1.5 multiplier is a convention, not a test, and on a right-skewed variable like ozone it will flag points routinely. Investigate before you delete anything.
7.4

7.4 boxplot() in R

boxplot(oz, ylab = "Ozone (ppb)", main = "Daily ozone, New York 1973")

# horizontal often reads better under a histogram
boxplot(oz, horizontal = TRUE, xlab = "Ozone (ppb)")

# the underlying numbers
boxplot.stats(oz)
$stats
[1]   1.0  18.0  31.5  63.5 122.0

$n
[1] 116

$out
[1] 135 168

The $stats vector holds the five plotted values in order: lower whisker, Q1, median, Q3, upper whisker. $out lists the flagged points and $n the sample size.

The Q3 reported here is 63.5, while quantile(oz, 0.75) gave 63.25. boxplot() uses hinges rather than R's default quantile definition — the same nine-definitions issue from Section 4.4. The difference is never material, but it is worth recognising rather than hunting for a bug.

7.5

7.5 Comparing groups side by side

This is what boxplots are genuinely best at. The formula interface y ~ g reads as "y broken down by g" and produces one box per group.

boxplot(weight ~ feed, data = chickwts,
        ylab = "Weight (g)", xlab = "Feed supplement",
        main = "Chick weight by feed type")
Chick weight by feed supplement, n = 71 casein horsebean linseed meatmeal soybean sunflower 100 168 236 304 372 440 Weight (g)
Figure 7.2: Six distributions compared on one common scale.

Casein and sunflower produce the heaviest chicks; horsebean is clearly worst. Sunflower also has the tightest box, meaning the most consistent results — a comparison of spread that a bar chart of group means would have concealed entirely.

7.6

7.6 What boxplots hide

A boxplot shows five numbers. Anything not captured by those five numbers is invisible — and that includes the number of peaks.

Boxplot: unremarkable waiting 40 52 64 76 88 100 Waiting time (min)
Histogram: two clear peaks 40 60 80 100 0 14 28 41 55 waiting (min) Frequency

Both panels show faithful$waiting. The boxplot is entirely well behaved: a symmetric-looking box, whiskers reaching the extremes, and not a single flagged outlier. The histogram beside it shows two separated groups.

Boxplots are unbeatable for comparing many groups at once, and blind to multimodality. Use them to compare distributions; use a histogram to understand one.
7.Q

Knowledge check — Section 7

A boxplot has Q1 = 20 and Q3 = 40. Beyond which upper value is an observation flagged?
The IQR is 40 − 20 = 20, so 1.5 × IQR = 30 and the upper fence sits at Q3 + 30 = 70. The upper whisker will then stop at the largest observation at or below 70, and anything above is drawn as an individual point.
A boxplot of faithful$waiting shows no outliers and a reasonably symmetric box. What can you conclude about the distribution?
A boxplot is built from five order statistics, and the number of peaks is simply not among them. This distribution has two well-separated clusters, yet every one of the five plotted numbers looks entirely ordinary. It is the clearest illustration in the lecture of why you should not summarise without also plotting.
Which task is a set of side-by-side boxplots best suited to?
Side-by-side boxplots put every group on one common scale, making differences in median and in IQR directly comparable at a glance. Detailed shape calls for a histogram, relationships between two numerical variables call for a scatter plot (Week 4), and accumulating proportions call for an ECDF.
8

8. Putting It Together

Choosing the right tool, and where we go next

8.1

8.1 Which plot, and when

QuestionUseWhy
What shape is this variable?HistogramOnly plot that shows peaks, gaps and skew clearly
What are the actual values?Stem-and-leafPreserves every observation; small n only
What proportion is below a threshold?ECDFRead the answer straight off the vertical axis
What is the 90th percentile?ECDF or quantile()No binning required, so no arbitrary choice
How do these six groups compare?Side-by-side boxplotsCentre and spread on one common scale
Are there unusual observations?BoxplotApplies the 1.5 × IQR rule automatically
Standard practice for a new continuous variable: run summary(), plot a histogram, then a boxplot. Three commands, and you will have caught skew, gaps, extreme values and missing data before doing anything else.
8.2

8.2 Function reference

FunctionPurposeWorth remembering
stem(x)Stem-and-leaf plotPrints as text; use scale to adjust
summary(x)Five-number summary + meanAlso reports the count of NAs
mean(x), median(x)CentreBoth need na.rm = TRUE if NAs present
range(x)Min and maxReturns two numbers, not their difference
quantile(x, p)Quantilesp may be a vector
IQR(x)Interquartile rangeEquals quantile(x,.75) - quantile(x,.25)
var(x), sd(x)SpreadAlways the sample versions, dividing by n−1
hist(x)Histogramfreq, breaks, xlab, main
ecdf(x)Cumulative relative frequencyReturns a function; wrap in plot()
boxplot(x)BoxplotFormula form: boxplot(y ~ g, data = d)
boxplot.stats(x)The numbers behind a boxplot$stats and $out
par(mfrow = c(r, c))Multiple plots per figureReset to c(1, 1) afterwards
8.3

8.3 Where this goes next

This week, in one line

A numerical summary compresses a distribution to a few numbers; a plot shows you what the compression threw away. Old Faithful is the case to remember — a perfectly ordinary mean, median, IQR and boxplot, concealing two entirely separate eruption regimes.

Before the next class

  • Work through the accompanying notebook. Every code block runs as given.
  • Answer the check question under each block from the output you obtain.
  • Try the same commands on quakes, islands or mtcars$mpg.

Week 4: two variables at a time

Everything so far has described a single variable in isolation. Next week we ask how two numerical variables move together:

  • Scatter plots
  • Correlation, and what it does and does not measure
  • Pictograms

A preview: plot(faithful$eruptions, faithful$waiting). The two clusters you saw in the histogram this week will separate cleanly in two dimensions, and the reason for them becomes obvious.

Contents