Two data sources. One grid of yin-yang glyphs. Both halves at a glance.
A geom_tile() heatmap gives you one number per cell. geom_taichi()
gives you two.
install.packages("ggtaichi")
# development version
devtools::install_github("PursuitOfDataScience/ggtaichi")Yin takes one source, yang the other. No decoration – every drop of ink is data.
library(ggtaichi)
library(ggplot2)
one <- data.frame(x = 1, y = 1, google = 7, twitter = 3)
ggplot(one, aes(x, y)) +
geom_taichi(yin = twitter, yang = google) +
coord_fixed() +
theme_taichi()pitts_small <- subset(pitts_tg, week <= 6)
ggplot(pitts_small, aes(week, category)) +
geom_taichi(yin = Twitter, yang = Google) +
theme_taichi()Covid and Masks lean dark – lots of Twitter – while staying pink, so
only moderate Google.
Six dimensions in one mark: x, y, two fills, two eyes.
quad <- data.frame(x = c(1, 2, 1, 2), y = c(2, 2, 1, 1),
yin = c(3, 5, 7, 9), yang = c(9, 7, 5, 3),
reach = c(10, 40, 25, 5), quality = c(2, 1, 4, 8))
ggplot(quad, aes(x, y)) +
geom_taichi(yin = yin, yang = yang, eyes = TRUE,
yin_eye_size = reach, yang_eye_size = quality,
limits = c(0, 10)) +
coord_fixed() +
theme_taichi()angle takes a constant or a column – a seventh channel.
rot <- data.frame(x = 1:4, y = 1, yin = 1:4, yang = 4:1,
turn = c(0, 45, 90, 135))
ggplot(rot, aes(x, y)) +
geom_taichi(yin = yin, yang = yang, angle = turn, eyes = TRUE,
limits = c(0, 5)) +
coord_fixed() +
theme_taichi()Hand angle to gganimate and it actually
spins.
library(gganimate)
spin <- expand.grid(x = 1:4, f = 1:48)
spin$y <- 1
spin$turn <- (spin$f - 1) * 7.5 + (spin$x - 1) * 45 # each one out of phase
ggplot(spin, aes(x, y)) +
geom_taichi(yin = 1, yang = 2, angle = turn, eyes = TRUE,
yin_colors = "grey15", yang_colors = "#C20824",
show.legend = FALSE) +
coord_fixed() +
theme_void() +
transition_states(f, transition_length = 1, state_length = 0)cafes_tg follows espresso and matcha across twelve weeks. Espresso
cools off, matcha warms up.
ggplot(cafes_tg, aes(neighbourhood, "")) +
geom_taichi(yin = matcha, yang = espresso, shared_legend = TRUE,
yin_name = "orders / 100 customers") +
theme_taichi() +
theme(axis.text.x = element_text(angle = 30, hjust = 1, size = 9)) +
labs(title = "Week {closest_state}", x = NULL) +
transition_states(week, transition_length = 2, state_length = 1)disc <- data.frame(x = c(1, 2, 1, 2), y = c(2, 2, 1, 1),
method = factor(c("A", "B", "C", "A")),
outcome = factor(c("win", "loss", "win", "loss")))
ggplot(disc, aes(x, y)) +
geom_taichi(yin = method, yang = outcome) +
coord_fixed() +
theme_taichi()The legend keys are little taichi as well.
ggplot(cafes_tg, aes(week, neighbourhood)) +
geom_taichi(yin = matcha, yang = espresso, shared_legend = TRUE,
yin_name = "orders / 100 customers") +
remove_padding() +
theme_taichi()Two fish in one spot tell you which. explicit computes the gap and
shows you how much. Cells where the two agree get no eye at all.
ggplot(cafes_tg, aes(week, neighbourhood)) +
geom_taichi(yin = matcha, yang = espresso, shared_legend = TRUE,
yin_name = "orders / 100 customers",
explicit = "difference") +
remove_padding() +
theme_taichi()Or as tilt, which the eye reads far more precisely. Upright means they agree.
tilt <- data.frame(x = 1:5, y = 1, yin = c(1, 3, 5, 7, 9), yang = 9:5)
ggplot(tilt, aes(x, y)) +
geom_taichi(yin = yin, yang = yang, shared_limits = TRUE,
explicit = "difference", explicit_channel = "angle") +
coord_fixed() +
theme_taichi()Animate it and the eyes blink shut exactly where the two sources cross.
ggplot(cafes_tg, aes(neighbourhood, "")) +
geom_taichi(yin = matcha, yang = espresso, shared_legend = TRUE,
yin_name = "orders / 100 customers",
explicit = "difference") +
theme_taichi() +
theme(axis.text.x = element_text(angle = 30, hjust = 1, size = 9)) +
labs(title = "Week {closest_state}", x = NULL) +
transition_states(week, transition_length = 2, state_length = 1)If the two ramps don’t span the same luminance, equal values don’t look equal and one fish quietly wins. Ask:
taichi_check_palette()
#> largest luminance mismatch : 40.6 L* (tolerance 5.0)
#> Verdict: FAILYes – the defaults fail their own check, and are kept only so old
figures don’t move. palette = "balanced" passes.
ggplot(cafes_tg, aes(week, neighbourhood)) +
geom_taichi(yin = matcha, yang = espresso,
palette = "balanced", shared_limits = TRUE) +
remove_padding() +
theme_taichi()ggplot(cafes_tg, aes(week, neighbourhood)) +
geom_taichi(yin = matcha, yang = espresso,
yin_scale = scale_taichi_yin_binned(n.breaks = 4),
yang_scale = scale_taichi_yang_binned(n.breaks = 4),
shared_limits = TRUE) +
remove_padding() +
theme_taichi()p <- ggplot(cafes_tg, aes(week, neighbourhood)) +
geom_taichi(yin = matcha, yang = espresso,
interactive = TRUE, data_id_by = "source")
ggiraph::girafe(ggobj = p)Hover one yin fish and every yin fish lights up. Live version in the gallery.
vignette("ggtaichi") for the full tour, vignette("animations") for
motion, and the
gallery
for the rest.
ggtaichi is a spinoff of the
ggDoubleHeat
package, which introduced the idea of folding two data sources into a
single reformed heat map. ggtaichi takes that two-scale design and
re-imagines the per-cell glyph as a taichi diagram.













