Describing distributions and relationships

A writing guide

Author

Dr. D and Claude

Purpose

You know how to write the code to make a plot and a table of summary statistics. This guide is about the next part: describing in words what they show. Communication of your findings is a critical part to being a good Data Scientist. Any time an assignment says describe, compare or interpret, refer to these guidelines.

The examples use the San Francisco Airbnb listings from hw02. Each one has a plot, summary statistics, and then an example paragraph. Since I am demonstrating quality writing, I am going to use plotting functions from the ggpubr and sjPlot package to also demonstrate quality plots.

library(tidyverse);library(gtsummary)
library(sjPlot);library(ggpubr)

airbnb <- read_csv(here::here("hw/data/listings.csv")) |>
  mutate(price = parse_number(price),
         superhost = factor(host_is_superhost,
                            labels = c("Not a superhost", "Superhost")))

The three-part recipe

Every description needs three things:

  1. A plot, with a title and axis labels that include units.
  2. Summary statistics.
  3. A few sentences that connect the numbers to the plot, in plain English, with units and context.
Note

Plots should never stand alone. They should always be accompanied by a sentence that describes the features of the plot, along with the summary numbers to put the data in context.

For a single variable it looks like this:

Quantitative variable Categorical variable
Plot histogram and boxplot bar chart (plot_frq())
Summary statistics n, min, Q1, median, mean, Q3, max, SD, IQR count and percent for each category
Sentences center, shape, spread, unusual values N and % for the largest category, plus anything that shouldn’t be there

Describing one variable

Quantitative variables

📖 Reading: IMS 5.3 Histograms and shape, IMS 5.6 Robust statistics, R4DS 10.3 Variation

Cover these four things:

  • Center: What’s a typical value? Use the median if the data are skewed or have outliers. Use the mean if they’re roughly symmetric.
  • Shape: Symmetric or skewed? One peak or more than one? The reading above explains the vocabulary.
  • Spread: How much do the values vary? The median goes with the IQR, the mean goes with the SD. Give the min and max too.
  • Unusual values: Outliers, impossible values, a pile-up at one number. Say what they are and what you did about them.

Example: review rating

review_scores_rating is the overall rating guests gave the listing, from 1 to 5.

gghistogram(airbnb$review_scores_rating, add = "mean", binwidth = 0.25)+
  labs(x = "Review score rating", y = "Count", title = "Distribution of review scores")
ggviolin(airbnb$review_scores_rating, add = c("jitter", "boxplot"), 
         add.params = list(jitter = 0.05, alpha = .5, size = .5)) + coord_flip() + 
  labs(y = "Review score rating", title = "Distribution of review scores")

summary(airbnb$review_scores_rating)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs 
  1.000   4.770   4.920   4.803   5.000   5.000    1584 
sd(airbnb$review_scores_rating, na.rm = TRUE)
[1] 0.3676205
TipGood Description

Review score ratings for Airbnb listings are strongly left skewed. Most listings are rated really high, 75% are above a 4.77, and a small number of low scores stretch the tail out down to a minimum of 1. There are actually a handful of ratings with a score of 1. The median rating is 4.92, higher than the mean of 4.80, which confirms the left skew. The standard deviation is 0.37 and the IQR is 0.23, indicating that typical scores range by about a quarter to a third of a point.

WarningNot enough

“The review scores are skewed. The mean is 4.8 and the median is 4.92.”

This doesn’t say which way it’s skewed, which center to trust, or how spread out the scores are. The descriptor is also on the wrong noun - the distribution of scores is skewed, not the scores themselves.

Categorical variables

📖 Reading: IMS 4.1 Contingency tables and bar plots, R4DS 1.4.1 A categorical variable

Cover these things:

  • What the categories are.
  • N and % for at least the largest category. The count by itself doesn’t tell me how common it is, and the percent by itself doesn’t tell me how much data it’s based on.
  • The smaller categories, if they matter.
  • Any category that shouldn’t be there (e.g. “Undefined”, “Other”, a typo).

For a binary variable you only need to describe one of the categories, since the other one is 100% minus that.

Example: room type

plot_frq(airbnb$room_type) + ylim(c(0, 5500)) + labs(x = "Room Type")

TipGood Description

Most of the listings are entire homes or apartments: 61.1% (n = 4,477) of the 7,332 listings. Private rooms are next at 35.2% (n = 2,579). Hotel rooms (3.3%, n = 243) and shared rooms (0.5%, n = 33) are rare. There are only the four room types and none are missing, so nothing looks off.

Binary example: superhost.

tbl_summary(airbnb, include = "superhost", 
            digits = all_categorical() ~ c(0, 1))
Characteristic N = 7,3321
superhost
    Not a superhost 3,817 (52.7%)
    Superhost 3,425 (47.3%)
    Unknown 90
1 n (%)
TipGood Description

Almost half (47.3%, n = 3,425) of the listings are hosted by a superhost. 90 listings (1.2%) are missing this information.

Describing a relationship

Before you plot anything, decide which variable is the explanatory variable (the one you think helps explain things, usually the x-axis) and which is the response (the outcome, usually the y-axis). In the Airbnb data a couple of natural questions are what goes along with the price of a listing, and what goes along with the host being a superhost.

The right plot and summary depend on the types of the two variables. A binary variable counts as categorical with two levels.

Response ~ Explanatory Plot Summary statistics Sentences
C ~ C side by side bars with row percents (plot_xtab()) counts and percents within each group (tbl_summary(by = )) the % in the response category for each explanatory category, and the difference in percentage points
Q ~ C faceted histograms (gghistogram(facet.by = )) or violin plots with boxplots (ggviolin()) grouped summarise() center, shape and spread for each group
Q ~ Q scatterplot with a smooth line correlation, cor() direction, strength and form
Note

Use numbers and comparison words. Say “higher” or “more spread out”, and then say by how much (higher by $120, a difference of 7.5 percentage points).

Categorical response, categorical explanatory (C ~ C)

📖 Reading: IMS 4.2 Visualizing two categorical variables, IMS 4.3 Row and column proportions, R4DS 10.5.2 Two categorical variables

Looking at counts only gives you half the story. Groups of different sizes can’t be fairly compared with counts, so compare the percents within each group of the explanatory variable (row percents). Put the explanatory variable in by = for tbl_summary(), and first in plot_xtab().

Example: room type and superhost

Is the type of room related to whether the host is a superhost? Room type is the explanatory variable and superhost is the response. Each column of the table is one room type, so the percents add up to 100% within each room type. tbl_summary() leaves the listings with no superhost value out of the percents, and counts them in the Unknown row.

tbl_summary(airbnb, by = room_type, include = superhost,
            digits = all_categorical() ~ c(0, 1))
Characteristic Entire home/apt
N = 4,4771
Hotel room
N = 2431
Private room
N = 2,5791
Shared room
N = 331
superhost



    Not a superhost 2,161 (48.3%) 238 (97.9%) 1,389 (55.8%) 29 (87.9%)
    Superhost 2,316 (51.7%) 5 (2.1%) 1,100 (44.2%) 4 (12.1%)
    Unknown 0 0 90 0
1 n (%)
plot_xtab(x = airbnb$room_type, grp = airbnb$superhost,
          show.total = FALSE, margin = "row") + labs(x = "Room Type")

TipGood Description

The percent of Airbnb listings hosted by a superhost differs by room type. About half of entire homes (51.7%, n = 2,316) are hosted by a superhost, compared with 44.2% (n = 1,100) of private rooms, a difference of 7.5 percentage points. Entire homes have more than twice as many superhosts as private rooms, but that is mostly because there are so many more entire homes, which is why the percents are the fairer comparison. Hotel rooms really stand out, with only 2.1% (5 of 243) hosted by a superhost. There are actually only 33 shared rooms, 12.1% of which have a superhost, which is too few listings to say much. All 90 listings with no superhost information are private rooms.

Quantitative response, categorical explanatory (Q ~ C)

📖 Reading: IMS 4.6 Comparing numerical data across groups, R4DS 10.5.1 A categorical and a numerical variable

Describe the center, shape and spread within each group, and use comparison words with numbers.

Example: number of reviews by superhost

Do superhosts get more reviews? Superhost is the explanatory variable and the number of reviews (number_of_reviews) is the response. I’m dropping the 90 listings with no superhost value so every listing is in one of the two groups.

airbnb_sh <- airbnb |> drop_na(superhost)

gghistogram(airbnb_sh, x = "number_of_reviews", binwidth = 25, fill = "superhost", facet.by = "superhost") +
  labs(x = "Number of reviews", y = "Count", title = "Number of reviews by superhost")
ggplot(airbnb_sh, aes(x = superhost, y = number_of_reviews, fill = superhost)) +
  geom_boxplot(width = .3, alpha = .8) + geom_violin(alpha = .3) +
  labs(x = "", y = "Number of reviews", title = "Number of reviews by superhost") + theme_bw() +
  coord_flip() + theme(legend.position = "none")

airbnb_sh |>
  group_by(superhost) |>
  summarise(
    n = n(),
    min = min(number_of_reviews),
    q1 = quantile(number_of_reviews, 0.25),
    median = median(number_of_reviews),
    mean = mean(number_of_reviews),
    sd = sd(number_of_reviews),
    iqr = IQR(number_of_reviews),
    max = max(number_of_reviews)
  )
# A tibble: 2 × 9
  superhost           n   min    q1 median  mean    sd   iqr   max
  <fct>           <int> <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl>
1 Not a superhost  3817     0     0      3  31.2  86.1    20  1500
2 Superhost        3425     0     5     24  91.5 149.    111  1292
TipGood Description

The number of reviews is strongly right skewed for both superhost and non-superhost listings, but superhost listings have many more reviews. The median superhost listing has 24 reviews, compared with only 3 for listings without a superhost. Both means are well above their medians (91.5 for superhosts and 31.2 for non-superhosts), so the median is the better way to compare. Superhost listings are also much more spread out, with an IQR of 111 reviews compared with 20 (SD = 149 and 86). There are actually a lot of listings with no reviews yet. Q1 is 0 for non-superhosts, so at least a quarter of those listings have none. Both groups have extreme values, with a maximum of 1,292 reviews for superhosts and 1,500 for non-superhosts.

Quantitative response, quantitative explanatory (Q ~ Q)

📖 Reading: IMS 5.1 Scatterplots for paired data, IMS 7.1.4 Describing linear relationships with correlation, R4DS 10.5.3 Two numerical variables

A scatterplot is the main tool. Describe three things:

  • Direction: Is the association positive or negative?
  • Strength: Is the relationship strong or weak? The correlation coefficient r helps here, but only for straight-line patterns.
  • Form: Is it linear? Curved? Clumped into clusters? Any outliers?
Important

The correlation only measures how well the points follow a straight line. That’s why you always have to look at the plot and not just report r.

Example: number of guests and price

How many people a listing sleeps (accommodates) is the explanatory variable and price is the response. Like in hw02, I’m only using listings priced at $1000 or less (airbnb_1k). That leaves out the 384 listings priced above $1000, and the 1,400 listings with no price.

There are thousands of points, so they pile on top of each other. Making them see-through (alpha) lets the crowded spots show up darker. The curvy line is a smooth trend and the red line is the best straight line.

airbnb_1k <- airbnb |> filter(price <= 1000)

ggplot(airbnb_1k, aes(x = accommodates, y = price)) +
  geom_point(alpha = 0.3) +
  geom_smooth(se = FALSE) +
  geom_smooth(method = "lm", se = FALSE, color = "red") +
  labs(x = "Number of guests the listing sleeps", y = "Price ($)",
       title = "Price by number of guests")

cor(airbnb_1k$accommodates, airbnb_1k$price)
[1] 0.5502468
TipGood Description

There is a strong positive association between the number of guests an Airbnb listing sleeps and its price per night (r = 0.55). The pattern is mostly linear, but it fans out. Listings that sleep 1 or 2 guests are bunched together at low prices, while prices for listings that sleep 6 or more are all over the place. Only listings priced at $1,000 or less are included, which chops off the top of the plot, so the biggest listings look cheaper here than they really are.

Sentence templates

These are just a starting point. Fill in the brackets, then wordsmith it so that it sounds more natural and somewhat like you wrote it. This is a spot where the more you practice writing the better you will get, but we all start by copying good examples then making it our own.

One quantitative variable

The distribution of [variable] is [symmetric / right skewed / left skewed]. The median [variable] is [value] [units] and half of the values are between [Q1] and [Q3] (IQR = [value]). Values range from [min] to [max]. [Anything unusual, and what you did about it.]

One categorical variable

The most common [variable] is [category] at [percent]% (n = [N]). [Next biggest categories.] [Anything unusual, like tiny categories or categories that shouldn’t be there.]

Categorical response ~ categorical explanatory

[Percent]% (n = [N]) of [group A] [response], compared with [percent]% (n = [N]) of [group B], a difference of [X] percentage points. [Anything surprising, or any group that is really small.]

Quantitative response ~ categorical explanatory

[Group A] tends to have [higher / lower] [variable] than [group B], with a median of [value] compared with [value]. [Group A] is [more / less] spread out (SD = [value] compared with [value]). [Shape of each group and any outliers.]

Quantitative response ~ quantitative explanatory

There is a [negligible / weak / moderate / strong / very strong] [positive / negative] association between [x] and [y] (r = [value]). The relationship is [linear / curved / clumped]. [Any outliers, or any place the pattern changes.]

Before you submit