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))
sepal widthsepal lengthspeciessetosaversicolorvirginica682.02.53.03.54.04.56868setosaversicolorvirginica

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))
sepal widthsepal lengthspeciessetosaversicolorvirginica2342344.55.05.56.06.57.07.58.0234setosaversicolorvirginica

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))
tiptotal billsexFemaleMale51010203040505101020304050FemaleMaleNoYes

Row labels appear on the right, column labels on top.

Faceted Histogram

(-> data/iris
    (sk/lay-histogram :sepal_length {:color :species})
    (sk/facet :species))
sepal lengthspeciessetosaversicolorvirginica6802468101214166868setosaversicolorvirginica

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))
tiptotal billsexFemaleMale51010203040505101020304050FemaleMaleNoYes

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}))
sepal widthsepal lengthspeciessetosaversicolorvirginica682.02.53.03.54.04.56868setosaversicolorvirginica

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}))
sepal widthsepal lengthspeciessetosaversicolorvirginica682.22.42.62.83.03.23.43.63.84.04.24.46868setosaversicolorvirginica

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))
3

Each 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}))
speciessetosaversicolorvirginica5678234246680122345012sepal lengthsepal widthpetal lengthpetal widthsepal lengthsepal widthpetal lengthpetal width

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}))
speciessetosaversicolorvirginica0510155sepal lengthsepal widthpetal lengthsepal lengthsepal widthpetal length

Faceted Bar Chart

(-> data/penguins
    (sk/lay-bar :species {:color :species})
    (sk/facet :island))
speciesspeciesAdelieGentooChinstrapAdelieGentooChinstrap020406080100120AdelieGentooChinstrapAdelieGentooChinstrapTorgersenBiscoeDream

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)"}))
Iris by SpeciesSepal Width (cm)Sepal Length (cm)speciessetosaversicolorvirginica682.02.53.03.54.04.56868setosaversicolorvirginica

What’s Next

source: notebooks/napkinsketch_book/faceting.clj