Data Visualization and Exploration

Being critical consumers and thoughtful producers

Challenges to human perception

Ranked best to worst in ease of perception.

  • Position
    • on a common scale
    • on unaligned scales
  • Length
  • Angle or slope
  • Area (size in 2D)
  • Depth in 3D
  • Color
    • brightness or luminance
    • saturation or intensity
  • Curvature
  • Volume (size in 3D)

Depending on the choice of graph, one or more of these elements could be involved.

[…] “learning styles” refers specifically to the theory that there are ways that individuals learn best. […] learning preferences suggest that there are ways people prefer to receive information, but it may not impact learning.

Sometimes our expectations work against our perception.

Contemporary example

Job cuts report from Challenger, Gray, and Christmas, found on Morning Brew.

Critique

Was the previous graph

  • clear?
  • convincing?
  • easy to read?

A case study in perception

We will explore data from a friend that shows assigned student letter grades originally and after a retake.

dat <- read.delim("../data/grade.csv", header = TRUE, sep = ',')
head(dat)
#>   student_code exam_no semester first_letter retake_letter
#> 1   student_21  Exam I      S22            F             F
#> 2   student_30  Exam I      S22            F             D
#> 3   student_35  Exam I      S22            F             F
#> 4   student_50  Exam I      S22            F             D
#> 5   student_53  Exam I      S22            F             A
#> 6   student_63  Exam I      S22            F             B

A pie chart of grades

layout(matrix(c(1, 2), nrow = 1))
par(mar = rep(0, 4))
pie(table(dat$first_letter))
mtext("Initial Grade", 3, line = -2, font = 2, cex = 1.35)
pie(table(dat$retake_letter))
mtext("Grade after Retake", 3, line = -2, font = 2, cex = 1.35)
Figure 2: Side-by-side pie charts showing grade distributions.

What do you notice, wonder, think?

Pie charts challenge us

We might be “used to” looking at pie charts, we might even have a “preference” for pie charts.

We should be careful with pie charts!

What problems, rooted in perception, could arise?

  • angle
  • area
  • color

What else could we try?

This might be a time to sketch.

But first, what does this data say?

What might we be trying to say?

Inspiration?

Three variants on bar charts (Lisa Charlotte Muth, 2022).

Three variants on bar charts (Lisa Charlotte Muth, 2022).

What issues (of perception) might arise?

Break for coding

Axis scales

The choices involved in presenting axis information are important to making the message as clear and intuitive as possible.

  • Text orientation, placement, (font, and size)
  • Label placement and format

Program defaults are often an aesthetic nightmare.

Exponential growth

Read in the data exponential.csv, give it a brief inspection, and make a plot that shows all three trials plotted against time. This could be done

  • column-by-column, or
  • all at once with matplot().
dat <- read.delim("./../data/exponential.csv", header = TRUE, sep = ',')
head(dat)
#>   time trial1 trial2  trial3
#> 1    0     10     10 10.0000
#> 2    1     10     20  5.0000
#> 3    2     10     40  2.5000
#> 4    3     10     80  1.2500
#> 5    4     10    160  0.6250
#> 6    5     10    320  0.3125
## matplot(...)

A closer look

Now plot just the first two rows. You can do this within the plot by putting a 1:2 in the first rows position of the data frame.

  • As measured by space, how much attention is given to halving versus doubling?
  • Is this “fair”?

Enter log scales

Properties of growth

Suppose we are interested in “percent change” as a metric.

Percent change Multiplier
700% 8
100% 2
0% 1
-50% \(\frac{1}{2}\)
-87.5% \(\frac{1}{8}\)
  • Which is “more important” doubling or halving?
  • How should each change be represented in a graph?

Spacing

Re-analysis Goals

Graph all three columns of the data in exponential.csv, first on linear scale and next on a log scale.

Observe how the use of vertical space draws attention to the “action” in the data. In other words, “what do you notice?”

Dealing with ugly

The default R axes are not necessarily pleasing.

par(mar = c(4.1, 5.1, 0.8, 0.8))
x <- 0:20
plot(x, 2^(x), ylab = "Doubling of values", xlab = "Number")
Figure 6

As it is, few things will change that.

Changing the scale

The default R axes are not necessarily pleasing.

par(mar = c(4.1, 5.1, 0.8, 0.8))
x <- 0:20
plot(x, 2^(x), ylab = "Doubling of values", xlab = "Number", log = 'y')
Figure 7

New questions

What to plot, what to label?

  • Do we plot the logarithm and have a “nicer” axis?
  • Do we plot the raw data and have more complex axis?

Regardless of choice, how do we make it user-friendly and “nice” aesthetically?

Improving aesthetics

Suppress axes with axes = F, but invite them back with the axis() command.

You’ll have to ask and answer

  • where are the ticks?”,
  • what are their labels?”, and
  • what is the vertical axis label?”.

Fortunately we do not have to worry about the horizontal axis (here representing “time”) because that pretty much only makes sense on a linear scale.

par(mar = c(4.1, 5.1, 0.8, 0.8))
x <- 0:20
plot(x, 2^(x), log = 'y')
Figure 8

Options for formatting (base R)

Suppose we wanted to show tick mark labels as powers of ten.

After setting axes = F, we could try a variety of things using axis(). We could

  • list at locations
  • list at locations and labels
  • list at locations and labels (one at a time)

Or we could (and will) use more advanced tools or specialized packages.

Comparing values or growth rates

f <- function(x)exp(x)
g <- function(x)exp((1 + 0.5*x)*x)
par(mar = c(4.1, 5.1, 0.8, 0.8))
plot(f, xlim = c(0, 2), ylim = c(0, g(2)))
plot(g, xlim = c(0, 2), col = 2, add = T)

Change in value.

Change in value.
f <- function(x)exp(x)
g <- function(x)exp((1 + 0.5*x)*x)
par(mar = c(4.1, 5.1, 0.8, 0.8))
plot(f, xlim = c(0, 2), ylim = c(1, g(2)), log = 'y')
plot(g, xlim = c(0, 2), col = 2, add = T, log = 'y')

Change in slope.

Change in slope.

A good rule of thumb

To illustrate value use a linear scale, to inspect rate of change use a log scale.

Consider the log-transformation of \(y = ae^{bt}\) which becomes \(\ln(y) = \ln(a) + bt\). (Verify this.)

For convenience we use the natural logarithm, but the rules work the same for any other choice of base.

This means the underlying exponential parameter \(b\) is emphasized as the slope when graphed on the log scale.

Boxplots

Recall the “preattentive pop-out” data stored in cards.csv.

dat <- read.delim("../data/cards2.csv", header = TRUE, sep=',')
dat <- na.omit(dat)
head(dat)
#>          ID Card Time1 Time2
#> 6      KL05    A  1.28  0.71
#> 7      KL05    B  1.28  1.03
#> 8      KL05    C  1.10  1.34
#> 9      KL05    D  2.10  0.83
#> 10     KL05    E  2.53  1.38
#> 11 John Doe    A  1.60  1.18

What vizualizations are relevant?

Boxplots

A basic boxplot, as we’ve seen, comes pretty quickly.

par(mar = c(4.1, 5.1, 0.8, 0.8))
boxplot(Time1 ~ Card, dat)
Figure 9: A boxplot of measured reaction times.

What does it mean?

Properties of a boxplot

It might help to know what in our data is reflected in the plot.

summary(dat[dat$Card== "D", "Time1"])
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>   0.960   2.045   3.400   3.633   5.060   7.940

A highly annotated boxplot.

A highly annotated boxplot.

More categorical variables

For simplicity we will switch to a built-in dataset, warpbreaks. Use ?warpbreaks to view its description and citation.

Roughly, it contains columns

  • breaks the number of breaks per length of yarn
  • wool the type of wool (A or B)
  • tension the weaving tension (L, M, H)
summary(warpbreaks)
#>      breaks      wool   tension
#>  Min.   :10.00   A:27   L:18   
#>  1st Qu.:18.25   B:27   M:18   
#>  Median :26.00          H:18   
#>  Mean   :28.15                 
#>  3rd Qu.:34.00                 
#>  Max.   :70.00

Multiple boxplots

par(mar = c(4.1, 5.1, 0.8, 0.8))
boxplot(breaks ~ wool, warpbreaks)
Figure 10: Number of breaks by wool type.
par(mar = c(4.1, 5.1, 0.8, 0.8))
boxplot(breaks ~ tension, warpbreaks)
Figure 11: Number of breaks by tension setting.

Combined boxplot

Notice that the order of specifying the variables matters!

par(mar = c(4.1, 5.1, 0.8, 0.8))
boxplot(breaks ~ wool*tension, warpbreaks)
Figure 12: Number of breaks by wool type within tension setting.
par(mar = c(4.1, 5.1, 0.8, 0.8))
boxplot(breaks ~ tension*wool, warpbreaks)
Figure 13: Number of breaks by tension setting within wool type.

Annotating boxplots

We could experiment with

  • replacing the variable codes with meaningful labels
  • coloring or shading the boxes in helpful ways

There are some interesting features that emerge.

  • Try specifying two or three colors for the second set of boxplots.
  • Do the colors appear in a helpful manner?

Extending boxplots

“Recently” boxplots (circa 1977) have evolved into violin plots (circa 1997).

  • Violin plots show the “shape” (in ways not equally appreciated by all) of the data.
  • We will revisit the idea, with caution, with ggplot.

More simply, boxplots can be overplotted with dots.

Boxplot with dots

par(mar = c(4.1, 5.1, 0.8, 0.8))
bxp <- boxplot(breaks ~ tension, warpbreaks)
points(breaks ~ tension, warpbreaks, pch = 19)
Figure 14: Number of breaks by tension setting within wool type. Raw data added.

Since many of the dots might overlap, we might be interested in incorporating horizontal jitter to alter the readability

Things get a little slippery here.

  • tension and wool are categorical, so it might take a bit of scratchwork to determine sensible, numerical input values.
  • It would be reasonable to create auxilliary variables that are numerical analogs (i.e., L “=” 1, M “=” 2, …).