Exploring a 78-row symbolic text dataset with tidytext and ggplot2

Hi everyone,

I wanted a compact dataset for testing a tidytext workflow where the same entities have two related text fields.

This example compares the words used in upright and reversed tarot card meanings. It is a simple frequency analysis rather than a sentiment model, but it produces a useful first view of how the two groups differ.

The data comes from the DeckAura tarot card meanings dataset. It contains 78 rows and includes separate upright_meaning and reversed_meaning columns.

Packages

install.packages(c("tidyverse", "tidytext"))

Reproducible example

library(tidyverse)
library(tidytext)

csv_url <- paste0(
  "https://huggingface.co/datasets/Blacik/",
  "deckaura-tarot-card-meanings/resolve/main/",
  "tarot_card_meanings.csv"
)

cards <- read_csv(
  csv_url,
  show_col_types = FALSE
)

required_columns <- c(
  "card_name",
  "upright_meaning",
  "reversed_meaning"
)

missing_columns <- setdiff(
  required_columns,
  names(cards)
)

if (length(missing_columns) > 0) {
  stop(
    "Missing columns: ",
    paste(missing_columns, collapse = ", ")
  )
}

stopifnot(nrow(cards) == 78)

tokens <- cards |>
  transmute(
    card_name,
    upright = upright_meaning,
    reversed = reversed_meaning
  ) |>
  pivot_longer(
    cols = c(upright, reversed),
    names_to = "orientation",
    values_to = "meaning"
  ) |>
  unnest_tokens(
    output = word,
    input = meaning
  ) |>
  anti_join(
    stop_words,
    by = "word"
  ) |>
  filter(
    str_detect(word, "^[a-z]+$")
  ) |>
  mutate(
    orientation = recode(
      orientation,
      upright = "Upright meanings",
      reversed = "Reversed meanings"
    )
  )

top_words <- tokens |>
  count(
    orientation,
    word,
    sort = TRUE
  ) |>
  group_by(orientation) |>
  slice_max(
    order_by = n,
    n = 15,
    with_ties = FALSE
  ) |>
  ungroup() |>
  mutate(
    word = reorder_within(
      word,
      n,
      orientation
    )
  )

plot <- ggplot(
  top_words,
  aes(
    x = n,
    y = word,
    fill = orientation
  )
) +
  geom_col(
    show.legend = FALSE
  ) +
  facet_wrap(
    vars(orientation),
    scales = "free_y"
  ) +
  scale_y_reordered() +
  labs(
    title = "Most frequent terms in tarot card meanings",
    subtitle = "Stop words removed before counting",
    x = "Word count",
    y = NULL
  ) +
  theme_minimal(
    base_size = 12
  ) +
  theme(
    plot.title.position = "plot",
    panel.grid.major.y = element_blank()
  )

print(plot)

ggsave(
  filename = "tarot-meaning-word-counts.png",
  plot = plot,
  width = 10,
  height = 6,
  dpi = 160
)

The transformation produces one tidy row per card, orientation and token. Stop words and non-alphabetic tokens are removed before counting.

One limitation is that raw frequency favors words that are common across the entire corpus. My next step would be to compare the two groups using weighted log odds or another measure of distinctiveness.

With only 78 paired documents, would you use weighted log odds, TF-IDF or a different method for finding terms that distinguish the two orientations?