22 Faceting
Faceting splits data into subsets and draws each in its own panel, making it easy to compare patterns across groups.
(ns plotje-book.faceting
(:require
;; Rdatasets -- standard datasets
[scicloj.metamorph.ml.rdatasets :as rdatasets]
;; Kindly -- notebook rendering protocol
[scicloj.kindly.v4.kind :as kind]
;; Plotje -- composable plotting
[scicloj.plotje.api :as pj]))Facet Wrap
pj/facet splits a pose into panels by one categorical column. The default direction is :col β a horizontal row of panels:
(-> (rdatasets/datasets-iris)
(pj/lay-point :sepal-length :sepal-width {:color :species})
(pj/facet :species))Each species gets its own panel with a strip label on top. Scales are shared by default β all panels use the same x and y range, making direct comparison easy.
Vertical Facet
Pass :row as the direction for a vertical column of panels:
(-> (rdatasets/datasets-iris)
(pj/lay-point :sepal-length :sepal-width {:color :species})
(pj/facet :species :row))Facet Grid
pj/facet-grid splits by two columns β one for rows, one for columns:
(-> (rdatasets/reshape2-tips)
(pj/lay-point :total-bill :tip {:color :sex})
(pj/facet-grid :smoker :sex))Row labels appear on the right, column labels on top.
Faceted Histogram
(-> (rdatasets/datasets-iris)
(pj/lay-histogram :sepal-length {:color :species})
(pj/facet :species))Faceted Regression
Layers compose with faceting β scatter plus regression per panel:
(-> (rdatasets/reshape2-tips)
(pj/pose :total-bill :tip {:color :sex})
pj/lay-point
(pj/lay-smooth {:stat :linear-model})
(pj/facet-grid :smoker :sex))Free Scales (Independent Axis Ranges)
By default all panels share the same axis ranges. Use the :scales option to let axes vary per panel.
Shared (default) β all panels carry the same x and y ranges:
(-> (rdatasets/datasets-iris)
(pj/lay-point :sepal-length :sepal-width {:color :species})
(pj/facet :species)
(pj/options {:scales :shared}))Inspect the coordinated domains directly:
(->> (-> (rdatasets/datasets-iris)
(pj/lay-point :sepal-length :sepal-width)
(pj/facet :species)
pj/plan
:panels)
(mapv :x-domain))[[4.225 8.05] [4.225 8.05] [4.225 8.05]]Free y β each panel has its own y-range:
(->> (-> (rdatasets/datasets-iris)
(pj/lay-point :sepal-length :sepal-width)
(pj/facet :species)
(pj/options {:scales :free-y})
pj/plan
:panels)
(mapv :y-domain))[[2.195 4.505000000000001] [1.93 3.4699999999999998] [2.12 3.88]]Other values: :free-x (x per-panel, y shared), :free (both axes per-panel).
Facet Plan Structure
Under the hood, faceting produces multiple panels in the plan:
(def faceted-plan
(-> (rdatasets/datasets-iris)
(pj/lay-point :sepal-length :sepal-width {:color :species})
(pj/facet :species)
pj/plan))(:grid faceted-plan){:rows 1, :cols 3}(count (:panels faceted-plan))3Each panel has a grid position and a strip label:
(:panels faceted-plan)[{:coord :cartesian,
:y-domain [1.93 4.505000000000001],
:col-label "setosa",
:x-scale {:type :linear},
:x-domain [4.225 8.05],
:x-ticks {:values [6.0 8.0], :labels ["6" "8"], :categorical? false},
:col 0,
:layers
[{:mark :point,
:style {:opacity 0.75, :radius 3.0},
:size-scale nil,
:alpha-scale nil,
:groups
[{:color
[0.8941176470588236 0.10196078431372549 0.10980392156862745 1.0],
:xs #tech.v3.dataset.column<float64>[50]
:sepal-length
[5.100, 4.900, 4.700, 4.600, 5.000, 5.400, 4.600, 5.000, 4.400, 4.900, 5.400, 4.800, 4.800, 4.300, 5.800, 5.700, 5.400, 5.100, 5.700, 5.100...],
:ys #tech.v3.dataset.column<float64>[50]
:sepal-width
[3.500, 3.000, 3.200, 3.100, 3.600, 3.900, 3.400, 3.400, 2.900, 3.100, 3.700, 3.400, 3.000, 3.000, 4.000, 4.400, 3.900, 3.500, 3.800, 3.800...],
:label "setosa",
:row-indices #tech.v3.dataset.column<int64>[50]
:__row-idx
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19...]}],
:y-domain [2.3 4.4],
:x-domain [4.3 5.8]}],
:y-scale {:type :linear},
:y-ticks
{:values [2.0 2.5 3.0 3.5 4.0 4.5],
:labels ["2.0" "2.5" "3.0" "3.5" "4.0" "4.5"],
:categorical? false},
:row 0}
{:coord :cartesian,
:y-domain [1.93 4.505000000000001],
:col-label "versicolor",
:x-scale {:type :linear},
:x-domain [4.225 8.05],
:x-ticks {:values [6.0 8.0], :labels ["6" "8"], :categorical? false},
:col 1,
:layers
[{:mark :point,
:style {:opacity 0.75, :radius 3.0},
:size-scale nil,
:alpha-scale nil,
:groups
[{:color
[0.21568627450980393 0.49411764705882355 0.7215686274509804 1.0],
:xs #tech.v3.dataset.column<float64>[50]
:sepal-length
[7.000, 6.400, 6.900, 5.500, 6.500, 5.700, 6.300, 4.900, 6.600, 5.200, 5.000, 5.900, 6.000, 6.100, 5.600, 6.700, 5.600, 5.800, 6.200, 5.600...],
:ys #tech.v3.dataset.column<float64>[50]
:sepal-width
[3.200, 3.200, 3.100, 2.300, 2.800, 2.800, 3.300, 2.400, 2.900, 2.700, 2.000, 3.000, 2.200, 2.900, 2.900, 3.100, 3.000, 2.700, 2.200, 2.500...],
:label "versicolor",
:row-indices #tech.v3.dataset.column<int64>[50]
:__row-idx
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19...]}],
:y-domain [2.0 3.4],
:x-domain [4.9 7.0]}],
:y-scale {:type :linear},
:y-ticks
{:values [2.0 2.5 3.0 3.5 4.0 4.5],
:labels ["2.0" "2.5" "3.0" "3.5" "4.0" "4.5"],
:categorical? false},
:row 0}
{:coord :cartesian,
:y-domain [1.93 4.505000000000001],
:col-label "virginica",
:x-scale {:type :linear},
:x-domain [4.225 8.05],
:x-ticks {:values [6.0 8.0], :labels ["6" "8"], :categorical? false},
:col 2,
:layers
[{:mark :point,
:style {:opacity 0.75, :radius 3.0},
:size-scale nil,
:alpha-scale nil,
:groups
[{:color
[0.30196078431372547 0.6862745098039216 0.2901960784313726 1.0],
:xs #tech.v3.dataset.column<float64>[50]
:sepal-length
[6.300, 5.800, 7.100, 6.300, 6.500, 7.600, 4.900, 7.300, 6.700, 7.200, 6.500, 6.400, 6.800, 5.700, 5.800, 6.400, 6.500, 7.700, 7.700, 6.000...],
:ys #tech.v3.dataset.column<float64>[50]
:sepal-width
[3.300, 2.700, 3.000, 2.900, 3.000, 3.000, 2.500, 2.900, 2.500, 3.600, 3.200, 2.700, 3.000, 2.500, 2.800, 3.200, 3.000, 3.800, 2.600, 2.200...],
:label "virginica",
:row-indices #tech.v3.dataset.column<int64>[50]
:__row-idx
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19...]}],
:y-domain [2.2 3.8],
:x-domain [4.9 7.9]}],
:y-scale {:type :linear},
:y-ticks
{:values [2.0 2.5 3.0 3.5 4.0 4.5],
:labels ["2.0" "2.5" "3.0" "3.5" "4.0" "4.5"],
:categorical? false},
:row 0}]A related multi-panel layout, the scatter plot matrix (SPLOM), uses pj/cross rather than pj/facet β the panels show all pairs of variables instead of one variable split across panels. See the Scatter chapter for the canonical SPLOM example.
Comparing Multiple Columns
Pass a vector of column names to create one panel per column:
(pj/lay-histogram (rdatasets/datasets-iris) [:sepal-length :sepal-width :petal-length] {:color :species})Faceted Bar Chart
(-> (rdatasets/palmerpenguins-penguins)
(pj/lay-bar :species {:color :species})
(pj/facet :island))Labels and Faceting
pj/options works with faceted plots:
(-> (rdatasets/datasets-iris)
(pj/lay-point :sepal-length :sepal-width {:color :species})
(pj/facet :species)
(pj/options {:title "Iris by Species"
:x-label "Sepal Length (cm)" :y-label "Sepal Width (cm)"}))Whatβs Next
- Troubleshooting β common issues and how to fix them
- API Reference β complete function listing with docstrings