AE 08: Explore coffee taste tests

Suggested answers

Application exercise
Answers
Modified

September 26, 2024

The Great American Coffee Taste Test

library(tidyverse)
library(skimr)
library(GGally)

# preferred theme
theme_set(theme_minimal(base_size = 12, base_family = "Atkinson Hyperlegible"))

In October 2023, James Hoffmann and coffee company Cometeer held the “Great American Coffee Taste Test” on YouTube, during which viewers were asked to fill out a survey about 4 coffees they ordered from Cometeer for the tasting. Tidy Tuesday published the data set we are using.

coffee_survey <- read_csv(file = "data/coffee_survey.csv")

It includes the following features:

variable class description
submission_id character Submission ID
age character What is your age?
cups character How many cups of coffee do you typically drink per day?
where_drink character Where do you typically drink coffee?
brew character How do you brew coffee at home?
brew_other character How else do you brew coffee at home?
purchase character On the go, where do you typically purchase coffee?
purchase_other character Where else do you purchase coffee?
favorite character What is your favorite coffee drink?
favorite_specify character Please specify what your favorite coffee drink is
additions character Do you usually add anything to your coffee?
additions_other character What else do you add to your coffee?
dairy character What kind of dairy do you add?
sweetener character What kind of sugar or sweetener do you add?
style character Before today’s tasting, which of the following best described what kind of coffee you like?
strength character How strong do you like your coffee?
roast_level character What roast level of coffee do you prefer?
caffeine character How much caffeine do you like in your coffee?
expertise numeric Lastly, how would you rate your own coffee expertise?
coffee_a_bitterness numeric Coffee A - Bitterness
coffee_a_acidity numeric Coffee A - Acidity
coffee_a_personal_preference numeric Coffee A - Personal Preference
coffee_a_notes character Coffee A - Notes
coffee_b_bitterness numeric Coffee B - Bitterness
coffee_b_acidity numeric Coffee B - Acidity
coffee_b_personal_preference numeric Coffee B - Personal Preference
coffee_b_notes character Coffee B - Notes
coffee_c_bitterness numeric Coffee C - Bitterness
coffee_c_acidity numeric Coffee C - Acidity
coffee_c_personal_preference numeric Coffee C - Personal Preference
coffee_c_notes character Coffee C - Notes
coffee_d_bitterness numeric Coffee D - Bitterness
coffee_d_acidity numeric Coffee D - Acidity
coffee_d_personal_preference numeric Coffee D - Personal Preference
coffee_d_notes character Coffee D - Notes
prefer_abc character Between Coffee A, Coffee B, and Coffee C which did you prefer?
prefer_ad character Between Coffee A and Coffee D, which did you prefer?
prefer_overall character Lastly, what was your favorite overall coffee?
wfh character Do you work from home or in person?
total_spend character In total, much money do you typically spend on coffee in a month?
why_drink character Why do you drink coffee?
why_drink_other character Other reason for drinking coffee
taste character Do you like the taste of coffee?
know_source character Do you know where your coffee comes from?
most_paid character What is the most you’ve ever paid for a cup of coffee?
most_willing character What is the most you’d ever be willing to pay for a cup of coffee?
value_cafe character Do you feel like you’re getting good value for your money when you buy coffee at a cafe?
spent_equipment character Approximately how much have you spent on coffee equipment in the past 5 years?
value_equipment character Do you feel like you’re getting good value for your money when you buy coffee at a cafe?
gender character Gender
gender_specify character Gender (please specify)
education_level character Education Level
ethnicity_race character Ethnicity/Race
ethnicity_race_specify character Ethnicity/Race (please specify)
employment_status character Employment Status
number_children character Number of Children
political_affiliation character Political Affiliation

Our ultimate goal on the next homework assignment is to predict which coffee a respondent will prefer based on their survey responses. We will use the prefer_overall variable as our target.

A quick {skimr} of the data:

skim(coffee_survey)
Data summary
Name coffee_survey
Number of rows 4042
Number of columns 57
_______________________
Column type frequency:
character 44
numeric 13
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
submission_id 0 1.00 6 6 0 4042 0
age 31 0.99 13 15 0 7 0
cups 93 0.98 1 11 0 6 0
where_drink 70 0.98 7 44 0 65 0
brew 385 0.90 5 165 0 449 0
brew_other 3364 0.17 2 319 0 160 0
purchase 3332 0.18 5 107 0 89 0
purchase_other 4011 0.01 4 83 0 26 0
favorite 62 0.98 5 32 0 12 0
favorite_specify 3926 0.03 3 92 0 78 0
additions 83 0.98 5 100 0 53 0
additions_other 3994 0.01 3 140 0 42 0
dairy 2356 0.42 8 110 0 175 0
sweetener 3530 0.13 5 99 0 82 0
style 84 0.98 4 11 0 12 0
strength 126 0.97 4 15 0 5 0
roast_level 102 0.97 4 7 0 7 0
caffeine 125 0.97 5 13 0 3 0
coffee_a_notes 1464 0.64 1 377 0 2317 0
coffee_b_notes 1586 0.61 1 980 0 2199 0
coffee_c_notes 1659 0.59 1 438 0 2163 0
coffee_d_notes 1454 0.64 1 528 0 2354 0
prefer_abc 270 0.93 8 8 0 3 0
prefer_ad 281 0.93 8 8 0 2 0
prefer_overall 272 0.93 8 8 0 4 0
wfh 518 0.87 18 26 0 3 0
total_spend 531 0.87 4 8 0 6 0
why_drink 474 0.88 5 93 0 84 0
why_drink_other 3875 0.04 2 195 0 163 0
taste 479 0.88 2 3 0 2 0
know_source 483 0.88 2 3 0 2 0
most_paid 515 0.87 5 13 0 8 0
most_willing 532 0.87 5 13 0 8 0
value_cafe 542 0.87 2 3 0 2 0
spent_equipment 536 0.87 7 16 0 7 0
value_equipment 548 0.86 2 3 0 2 0
gender 519 0.87 4 22 0 5 0
gender_specify 4030 0.00 2 28 0 11 0
education_level 604 0.85 15 34 0 6 0
ethnicity_race 624 0.85 15 29 0 6 0
ethnicity_race_specify 3937 0.03 2 53 0 82 0
employment_status 623 0.85 7 18 0 6 0
number_children 636 0.84 1 11 0 5 0
political_affiliation 753 0.81 8 14 0 4 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
expertise 104 0.97 5.69 1.95 1 5 6 7 10 ▂▃▇▇▁
coffee_a_bitterness 244 0.94 2.14 0.95 1 1 2 3 5 ▅▇▃▂▁
coffee_a_acidity 263 0.93 3.63 0.98 1 3 4 4 5 ▁▂▅▇▃
coffee_a_personal_preference 253 0.94 3.31 1.19 1 2 3 4 5 ▂▅▆▇▅
coffee_b_bitterness 262 0.94 3.01 0.99 1 2 3 4 5 ▂▅▇▆▁
coffee_b_acidity 275 0.93 2.22 0.87 1 2 2 3 5 ▃▇▅▁▁
coffee_b_personal_preference 269 0.93 3.07 1.11 1 2 3 4 5 ▂▆▇▆▃
coffee_c_bitterness 278 0.93 3.07 1.00 1 2 3 4 5 ▁▅▇▆▂
coffee_c_acidity 291 0.93 2.37 0.92 1 2 2 3 5 ▃▇▆▂▁
coffee_c_personal_preference 276 0.93 3.06 1.13 1 2 3 4 5 ▂▆▇▆▃
coffee_d_bitterness 275 0.93 2.16 1.08 1 1 2 3 5 ▇▇▅▂▁
coffee_d_acidity 277 0.93 3.86 1.01 1 3 4 5 5 ▁▂▃▇▆
coffee_d_personal_preference 278 0.93 3.38 1.45 1 2 4 5 5 ▅▃▃▆▇

Examining continuous variables

Your turn: Examine expertise using a histogram and appropriate binwidth. Describe the features of this variable.

ggplot(data = coffee_survey, mapping = aes(x = expertise)) +
  geom_histogram(bins = 10, color = "white")

Add response here.

  • Unimodal
  • Slight skew (average expertise around 6)
  • Few consider themselves experts
  • Unsurprising, given the non-representative sample (who else would participate in a taste test except coffee enthusiasts?)

Your turn: Each coffee has three numeric ratings by the respondents: bitterness, acidity, and personal preference. Create a histogram for each of these characteristics, faceted by coffee type. What do you notice?

Wrangling the data for easier visualization

The original structure of the data is one column for each coffee for each characteristic. You could create separate graphs for each of the 12 columns, but that seems like a lot of work. Instead, consider using the pivot_longer() function to restructure the data to one row per coffee per characteristic. This will make it easier to create the faceted histograms.

coffee_survey |>
  select(starts_with("coffee"), -ends_with("notes")) |>
  pivot_longer(
    cols = everything(),
    names_to = c("coffee", "measure"),
    names_prefix = "coffee_",
    names_sep = "_",
    values_to = "value"
  )
# A tibble: 48,504 × 3
   coffee measure    value
   <chr>  <chr>      <dbl>
 1 a      bitterness    NA
 2 a      acidity       NA
 3 a      personal      NA
 4 b      bitterness    NA
 5 b      acidity       NA
 6 b      personal      NA
 7 c      bitterness    NA
 8 c      acidity       NA
 9 c      personal      NA
10 d      bitterness    NA
# ℹ 48,494 more rows
coffee_survey |>
  select(starts_with("coffee"), -ends_with("notes")) |>
  pivot_longer(
    cols = everything(),
    names_to = c("coffee", "measure"),
    names_prefix = "coffee_",
    names_sep = "_",
    values_to = "value"
  ) |>
  ggplot(mapping = aes(x = value)) +
  geom_bar() +
  facet_grid(rows = vars(coffee), cols = vars(measure))

Add response here.

  • Coffees A and D skew more acidic compared to B and C
  • Coffees A and D are less bitter than B and C
  • D tends to have higher personal preference, skews higher distribution overall
  • A also skews a bit higher personal preference, whereas B and C are more normally distributed

Examining categorical variables

Your turn: Examine prefer_overall graphically. Record your observations.

coffee_survey |>
  mutate(prefer_overall = fct_infreq(prefer_overall) |> fct_rev()) |>
  ggplot(mapping = aes(y = prefer_overall)) +
  geom_bar()

Add response here.

  • Four possible choices - different kind of classification problem than in the past
  • Coffee D is the most popular overall, similar levels of support for A/B/C
  • Some respondents did not select any of the four - will need to drop in the modeling stage
  • Is this preference gap large enough to necessitate undersampling or another class imbalance procedure?

Your turn: Examine cups, brew, and roast_level. Record your observations, in particular how you might need to handle these variables in the modeling stage.

# cups
ggplot(data = coffee_survey, mapping = aes(y = cups)) +
  geom_bar()

# correct order
coffee_survey |>
  mutate(cups = fct(cups) |>
    fct_relevel("Less than 1", "1", "2", "3", "4", "More than 4")) |>
  ggplot(mapping = aes(y = cups)) +
  geom_bar()

Add response here.

  • Typical drinker is 1-2 cups per day
  • A decent number of occasional coffee drinkers
  • Long tail - some respondents drink a lot of coffee each day (I can’t imagine the number of bathroom trips…)
ggplot(data = coffee_survey, mapping = aes(y = brew)) +
  geom_bar()

Add response here.

Oh crap. Each value contains potentially multiple types of brews. This is not useful in its current form. Need to split it up into one row per respondent per brew type.

coffee_survey |>
  select(brew) |>
  separate_longer_delim(
    cols = brew,
    delim = ", "
  ) |>
  mutate(brew = fct_infreq(brew) |> fct_rev()) |>
  ggplot(mapping = aes(y = brew)) +
  geom_bar()

Bonus fun! How do the brew types correlate with one another?

This could be an issue later with modeling if we do dummy encoding.

library(ggcorrplot) # for the heatmap
library(colorspace) # for the diverging color scale

coffee_survey |>
  # get into a long format
  separate_longer_delim(
    cols = brew,
    delim = ", "
  ) |>
  # frequency count for each respondent and brew type
  count(submission_id, brew) |>
  # remove NAs
  drop_na() |>
  # restructure to one row per respondent, one column per brew type
  # fill in NAs with 0
  pivot_wider(names_from = brew, values_from = n, values_fill = 0) |>
  # drop submission_id - don't need anymore
  select(-submission_id) |>
  # calculate correlation coefficients
  cor() |>
  # draw the correlation matrix as a heatmap
  ggcorrplot(
    # order the variables by correlation values
    hc.order = TRUE,
    # just show the lower triangle
    type = "lower",
    # make each box a white border
    outline.color = "white"
  ) +
  # more optimal color palette
  scale_fill_continuous_diverging()

ggplot(data = coffee_survey, mapping = aes(y = roast_level)) +
  geom_bar()

coffee_survey |>
  mutate(roast_level = fct_infreq(roast_level) |> fct_rev()) |>
  ggplot(mapping = aes(y = roast_level)) +
  geom_bar()

Add response here.

By far the most common roast preferences are light, medium, and dark. Others are so infrequent they might not be useful.

Some additional variables for fun

coffee_survey |>
  mutate(favorite = fct_infreq(favorite) |> fct_rev()) |>
  ggplot(mapping = aes(y = favorite)) +
  geom_bar()

  • Pourover is most popular (coffee snobs)
  • Uneven distribution - some are distinctly less popular than others. Might be worth testing to collapse into “Other” category or hashing/effect encoding
  • Not a ton of unique categories though
ggplot(data = coffee_survey, mapping = aes(y = political_affiliation)) +
  geom_bar()

  • Skews heavily Democratic
  • Is this something that would even be useful?

Making comparisons

Your turn: Compare the relationship between overall preference and the respondents’ preferred roast levels. Use a proportional bar chart to visualize the relationship.

Tip

Use position = "fill" with geom_bar() to automatically calculate percentages for the chart.

coffee_survey |>
  filter(roast_level %in% c("Dark", "Medium", "Light")) |>
  mutate(
    roast_level = factor(roast_level,
      levels = c("Light", "Medium", "Dark")
    )
  ) |>
  count(prefer_overall, roast_level) |>
  drop_na() |>
  mutate(n_pct = n / sum(n), .by = prefer_overall) |>
  mutate(prefer_overall = fct_reorder(.f = prefer_overall, .x = n_pct, .fun = first)) |>
  ggplot(mapping = aes(y = prefer_overall, x = n_pct, fill = fct_rev(roast_level))) +
  geom_col() +
  scale_fill_discrete_sequential(rev = FALSE, guide = guide_legend(rev = TRUE))

Add response here.

  • People who prefer coffees D and A like light roasts
  • People who prefer B and C mostly like medium roasts

Your turn: Examine the relationship between the respondents’ numeric ratings for acidity, bitterness, and personal preference for each of the four coffees and compare to their overall preference. Record your observations.

pref_by_traits <- coffee_survey |>
  select(starts_with("coffee"), -ends_with("notes"), prefer_overall) |>
  pivot_longer(
    cols = -prefer_overall,
    names_to = c("coffee", "measure"),
    names_prefix = "coffee_",
    names_sep = "_",
    values_to = "value"
  ) |>
  drop_na() |>
  mutate(coffee = fct_rev(coffee))

ggplot(data = pref_by_traits, mapping = aes(x = factor(value), y = coffee)) +
  geom_count() +
  facet_grid(rows = vars(prefer_overall), cols = vars(measure))

ggplot(data = pref_by_traits, mapping = aes(x = factor(value), y = coffee)) +
  geom_bin2d() +
  scale_fill_continuous_sequential() +
  facet_grid(rows = vars(prefer_overall), cols = vars(measure))

ggplot(data = pref_by_traits, mapping = aes(x = value, color = fct_rev(coffee))) +
  geom_freqpoly(binwidth = 1) +
  scale_fill_discrete_qualitative() +
  facet_grid(rows = vars(prefer_overall), cols = vars(measure))

ggplot(data = pref_by_traits, mapping = aes(x = value, y = coffee)) +
  geom_boxplot() +
  facet_grid(rows = vars(prefer_overall), cols = vars(measure))

Add response here.

People who prefer coffee A

  • Rate it higher for personal preference and acidity
  • Rate it lower for bitterness

People who prefer coffee B

  • Rate it higher for personal preference, mid for bitterness, and lower for acidity

People who prefer coffee C

  • Rate it higher for personal preference, mid for bitterness, and lower for acidity

People who prefer coffee D

  • Rate it higher for personal preference and acidity, lower for bitterness

Overall, seems like coffees A/D and B/C have similar evaluations

Data quality

Missingness

Demonstration: Use {visdat} to visualize missingness patterns in the data set.

library(visdat)

# quick glance of missingness by row/column order
vis_dat(coffee_survey)

# reorder columns based on % missing
vis_miss(coffee_survey, sort_miss = TRUE)

# cluster rows based on similarity in missingness patterns
vis_miss(coffee_survey, cluster = TRUE)

Your turn: Record your observations on the missingness patterns in the data set. What variables have high missingness? Is this surprising? What might you do to variables or observations with high degrees of missingness?

Add response here.

  • Some missingness throughout the dataset
  • Some variables are almost entirely missing (gender specify, lots of “other” columns). A lot of this is unsurprising since they are columns only to catch specific conditions or exceptions to other columns
  • “Sweetener” is unusually high missingness - is this because people don’t use sweeteners or because they didn’t answer the question?
  • Some observations have missing values for almost all the columns - are these valid observations? Is there enough info there to include in resulting models?

Outliers

Demonstration: Generate a scatterplot matrix for all the numeric variables in the data set.1

1 Not particularly helpful for this dataset, but a good practice to get into.

coffee_survey |>
  select(where(is.numeric)) |>
  ggpairs()

Your turn: Examine the distribution of roast/gender and roast/cups. Describe the patterns you see and anything that is of particular interest given the model we will estimate.

coffee_survey |>
  filter(roast_level %in% c("Dark", "Medium", "Light")) |>
  mutate(
    roast_level = factor(roast_level,
      levels = c("Light", "Medium", "Dark")
    )
  ) |>
  ggplot(mapping = aes(x = roast_level, y = gender)) +
  geom_count()

coffee_survey |>
  filter(roast_level %in% c("Dark", "Medium", "Light")) |>
  mutate(
    roast_level = factor(roast_level,
      levels = c("Light", "Medium", "Dark")
    ),
    cups = fct(cups) |>
      fct_relevel("Less than 1", "1", "2", "3", "4", "More than 4")
  ) |>
  ggplot(mapping = aes(x = roast_level, y = cups)) +
  geom_count()

coffee_survey |>
  filter(roast_level %in% c("Dark", "Medium", "Light")) |>
  mutate(
    roast_level = factor(roast_level,
      levels = c("Light", "Medium", "Dark")
    ),
    cups = fct(cups) |>
      fct_relevel("Less than 1", "1", "2", "3", "4", "More than 4")
  ) |>
  ggplot(mapping = aes(x = roast_level, y = cups)) +
  geom_bin2d() +
  scale_fill_continuous_sequential(limits = c(0, NA))

Add response here.

1-2 cups of light and medium roast are the most popular combinations. Everything else is significantly underrepresented. This could be a problem for the model if we don’t have enough data to make accurate predictions for these categories.

sessioninfo::session_info()
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.4.1 (2024-06-14)
 os       macOS Sonoma 14.6.1
 system   aarch64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/New_York
 date     2024-09-30
 pandoc   3.4 @ /usr/local/bin/ (via rmarkdown)

─ Packages ───────────────────────────────────────────────────────────────────
 package      * version date (UTC) lib source
 archive        1.1.8   2024-04-28 [1] CRAN (R 4.4.0)
 base64enc      0.1-3   2015-07-28 [1] CRAN (R 4.3.0)
 bit            4.0.5   2022-11-15 [1] CRAN (R 4.3.0)
 bit64          4.0.5   2020-08-30 [1] CRAN (R 4.3.0)
 cli            3.6.3   2024-06-21 [1] CRAN (R 4.4.0)
 codetools      0.2-20  2024-03-31 [1] CRAN (R 4.4.1)
 colorspace   * 2.1-1   2024-07-26 [1] CRAN (R 4.4.0)
 crayon         1.5.3   2024-06-20 [1] CRAN (R 4.4.0)
 digest         0.6.35  2024-03-11 [1] CRAN (R 4.3.1)
 dplyr        * 1.1.4   2023-11-17 [1] CRAN (R 4.3.1)
 evaluate       0.24.0  2024-06-10 [1] CRAN (R 4.4.0)
 fansi          1.0.6   2023-12-08 [1] CRAN (R 4.3.1)
 farver         2.1.2   2024-05-13 [1] CRAN (R 4.3.3)
 fastmap        1.2.0   2024-05-15 [1] CRAN (R 4.4.0)
 forcats      * 1.0.0   2023-01-29 [1] CRAN (R 4.3.0)
 generics       0.1.3   2022-07-05 [1] CRAN (R 4.3.0)
 GGally       * 2.2.1   2024-02-14 [1] CRAN (R 4.4.0)
 ggcorrplot   * 0.1.4.1 2023-09-05 [1] CRAN (R 4.4.0)
 ggplot2      * 3.5.1   2024-04-23 [1] CRAN (R 4.3.1)
 ggstats        0.6.0   2024-04-05 [1] CRAN (R 4.4.0)
 glue           1.7.0   2024-01-09 [1] CRAN (R 4.3.1)
 gtable         0.3.5   2024-04-22 [1] CRAN (R 4.3.1)
 here           1.0.1   2020-12-13 [1] CRAN (R 4.3.0)
 hms            1.1.3   2023-03-21 [1] CRAN (R 4.3.0)
 htmltools      0.5.8.1 2024-04-04 [1] CRAN (R 4.3.1)
 htmlwidgets    1.6.4   2023-12-06 [1] CRAN (R 4.3.1)
 jsonlite       1.8.8   2023-12-04 [1] CRAN (R 4.3.1)
 knitr          1.47    2024-05-29 [1] CRAN (R 4.4.0)
 labeling       0.4.3   2023-08-29 [1] CRAN (R 4.3.0)
 lifecycle      1.0.4   2023-11-07 [1] CRAN (R 4.3.1)
 lubridate    * 1.9.3   2023-09-27 [1] CRAN (R 4.3.1)
 magrittr       2.0.3   2022-03-30 [1] CRAN (R 4.3.0)
 munsell        0.5.1   2024-04-01 [1] CRAN (R 4.3.1)
 pillar         1.9.0   2023-03-22 [1] CRAN (R 4.3.0)
 pkgconfig      2.0.3   2019-09-22 [1] CRAN (R 4.3.0)
 plyr           1.8.9   2023-10-02 [1] CRAN (R 4.3.1)
 purrr        * 1.0.2   2023-08-10 [1] CRAN (R 4.3.0)
 R6             2.5.1   2021-08-19 [1] CRAN (R 4.3.0)
 RColorBrewer   1.1-3   2022-04-03 [1] CRAN (R 4.3.0)
 Rcpp           1.0.12  2024-01-09 [1] CRAN (R 4.3.1)
 readr        * 2.1.5   2024-01-10 [1] CRAN (R 4.3.1)
 repr           1.1.7   2024-03-22 [1] CRAN (R 4.4.0)
 reshape2       1.4.4   2020-04-09 [1] CRAN (R 4.3.0)
 rlang          1.1.4   2024-06-04 [1] CRAN (R 4.3.3)
 rmarkdown      2.27    2024-05-17 [1] CRAN (R 4.4.0)
 rprojroot      2.0.4   2023-11-05 [1] CRAN (R 4.3.1)
 rstudioapi     0.16.0  2024-03-24 [1] CRAN (R 4.3.1)
 scales         1.3.0   2023-11-28 [1] CRAN (R 4.4.0)
 sessioninfo    1.2.2   2021-12-06 [1] CRAN (R 4.3.0)
 skimr        * 2.1.5   2022-12-23 [1] CRAN (R 4.3.0)
 stringi        1.8.4   2024-05-06 [1] CRAN (R 4.3.1)
 stringr      * 1.5.1   2023-11-14 [1] CRAN (R 4.3.1)
 tibble       * 3.2.1   2023-03-20 [1] CRAN (R 4.3.0)
 tidyr        * 1.3.1   2024-01-24 [1] CRAN (R 4.3.1)
 tidyselect     1.2.1   2024-03-11 [1] CRAN (R 4.3.1)
 tidyverse    * 2.0.0   2023-02-22 [1] CRAN (R 4.3.0)
 timechange     0.3.0   2024-01-18 [1] CRAN (R 4.3.1)
 tzdb           0.4.0   2023-05-12 [1] CRAN (R 4.3.0)
 utf8           1.2.4   2023-10-22 [1] CRAN (R 4.3.1)
 vctrs          0.6.5   2023-12-01 [1] CRAN (R 4.3.1)
 visdat       * 0.6.0   2023-02-02 [1] CRAN (R 4.3.0)
 vroom          1.6.5   2023-12-05 [1] CRAN (R 4.3.1)
 withr          3.0.1   2024-07-31 [1] CRAN (R 4.4.0)
 xfun           0.45    2024-06-16 [1] CRAN (R 4.4.0)
 yaml           2.3.8   2023-12-11 [1] CRAN (R 4.3.1)

 [1] /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library

──────────────────────────────────────────────────────────────────────────────