18 Faceting
Faceting splits data into subsets and draws each in its own panel, making it easy to compare patterns across groups.
(ns napkinsketch-book.faceting
(:require
;; Shared datasets for these docs
[napkinsketch-book.datasets :as data]
;; Kindly β notebook rendering protocol
[scicloj.kindly.v4.kind :as kind]
;; Napkinsketch β composable plotting
[scicloj.napkinsketch.api :as sk]))Facet Wrap
sk/facet splits views by one categorical column. The default layout is a horizontal row of panels:
(-> data/iris
(sk/lay-point :sepal_length :sepal_width {:color :species})
(sk/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 :col as the direction for a vertical column of panels:
(-> data/iris
(sk/lay-point :sepal_length :sepal_width {:color :species})
(sk/facet :species :col))Facet Grid
sk/facet-grid splits by two columns β one for rows, one for columns:
(-> data/tips
(sk/lay-point :total_bill :tip {:color :sex})
(sk/facet-grid :smoker :sex))Row labels appear on the right, column labels on top.
Faceted Histogram
(-> data/iris
(sk/lay-histogram :sepal_length {:color :species})
(sk/facet :species))Faceted Regression
Layers compose with faceting β scatter plus regression per panel:
(-> data/tips
(sk/view :total_bill :tip {:color :sex})
sk/lay-point
sk/lay-lm
(sk/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 have the same y-range:
(-> data/iris
(sk/lay-point :sepal_length :sepal_width {:color :species})
(sk/facet :species)
(sk/options {:scales :shared}))Free y β each panel has its own y-range:
(-> data/iris
(sk/lay-point :sepal_length :sepal_width {:color :species})
(sk/facet :species)
(sk/options {:scales :free-y}))Other options: :free-x, :free (both axes free).
Facet Plan Structure
Under the hood, faceting produces multiple panels in the plan:
(def faceted-pl
(-> data/iris
(sk/lay-point :sepal_length :sepal_width {:color :species})
(sk/facet :species)
sk/plan))(:grid faceted-pl){:rows 1, :cols 3}(count (:panels faceted-pl))3Each panel has a grid position and a strip label:
(:panels faceted-pl)[{:coord :cartesian,
:y-domain [1.88 4.5200000000000005],
:col-label "setosa",
:x-scale {:type :linear},
:x-domain [4.12 8.08],
:x-ticks {:values [6.0 8.0], :labels ["6" "8"], :categorical? false},
:col 0,
:layers
[{:mark :point,
:style {:opacity 0.75, :radius 3.0},
: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.88 4.5200000000000005],
:col-label "versicolor",
:x-scale {:type :linear},
:x-domain [4.12 8.08],
:x-ticks {:values [6.0 8.0], :labels ["6" "8"], :categorical? false},
:col 1,
:layers
[{:mark :point,
:style {:opacity 0.75, :radius 3.0},
: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.88 4.5200000000000005],
:col-label "virginica",
:x-scale {:type :linear},
:x-domain [4.12 8.08],
:x-ticks {:values [6.0 8.0], :labels ["6" "8"], :categorical? false},
:col 2,
:layers
[{:mark :point,
:style {:opacity 0.75, :radius 3.0},
: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}]SPLOM (Scatter Plot Matrix)
sk/cross generates all pairs of columns. Combined with the multi-variable layout, this produces a scatter plot matrix (SPLOM):
(def cols [:sepal_length :sepal_width :petal_length :petal_width])(-> data/iris
(sk/view (sk/cross cols cols))
(sk/lay-point {:color :species}))Diagonal panels (where x = y) show points along the identity line since every row has the same value for both axes. Off-diagonal panels share scales per column (x) and per row (y), so each column of plots has the same x-axis and each row has the same y-axis.
Distribution Helper
sk/distribution creates diagonal views β one histogram per column:
(-> (sk/distribution data/iris :sepal_length :sepal_width :petal_length)
(sk/lay-histogram {:color :species}))Faceted Bar Chart
(-> data/penguins
(sk/lay-bar :species {:color :species})
(sk/facet :island))Labels and Faceting
sk/options works with faceted plots:
(-> data/iris
(sk/lay-point :sepal_length :sepal_width {:color :species})
(sk/facet :species)
(sk/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