23 Troubleshooting
Common mistakes and how to fix them.
(ns plotje-book.troubleshooting
(:require
;; Rdatasets -- standard datasets
[scicloj.metamorph.ml.rdatasets :as rdatasets]
;; Kindly -- notebook rendering protocol
[scicloj.kindly.v4.kind :as kind]
;; Tablecloth -- dataset manipulation
[tablecloth.api :as tc]
;; Plotje -- composable plotting
[scicloj.plotje.api :as pj]))Column Not Found
Symptom: "Column :foo (from :x) not found in dataset" error, listing the available columns at the end of the message.
Cause: The column reference does not match a dataset column name. Matching is strict – :foo matches keyword column :foo only, and "foo" matches string column "foo" only. The two forms do not interchange. Three common triggers:
1. Typo. A misspelled column name. Always check the spelling against the dataset’s actual columns:
(tc/column-names (rdatasets/datasets-iris))(:rownames
:sepal-length
:sepal-width
:petal-length
:petal-width
:species)2. Keyword vs string. A CSV loaded without :key-fn keyword produces string column names; using a keyword reference against that dataset throws:
(try
(-> (tc/dataset {"sepal_length" [5.0 6.0] "sepal_width" [3.0 3.5]})
(pj/pose :sepal_length :sepal_width)
pj/lay-point pj/plot)
(catch clojure.lang.ExceptionInfo e (ex-message e)))"Column :sepal_width (from :y) not found in dataset. Available: (\"sepal_length\" \"sepal_width\")"The fix is to either pass {:key-fn keyword} when loading the CSV (so the dataset has keyword columns) or to use string references everywhere.
3. Whitespace or punctuation mismatch. A column literally named "sepal length" (with a space) does not match :sepal-length (with a hyphen):
(try
(-> (tc/dataset {"sepal length" [5.0 6.0] "sepal width" [3.0 3.5]})
(pj/pose :sepal-length :sepal-width)
pj/lay-point pj/plot)
(catch clojure.lang.ExceptionInfo e (ex-message e)))"Column :sepal-width (from :y) not found in dataset. Available: (\"sepal length\" \"sepal width\")"Note that :key-fn keyword on "sepal length" produces :sepal length – a keyword whose printed form contains a space, not the hyphenated form a Clojure reader would normally produce. Spaces and other special characters in CSV headers usually need a custom :key-fn, e.g. (comp keyword #(clojure.string/replace % " " "-")).
Wrong Chart Type from Inference
Symptom: pj/pose produces a chart type that isn’t what you wanted – a boxplot when you wanted individual points, a line when you wanted a scatter.
Cause: pj/pose infers the layer type from column types. The defaults fit the most common use case for each column-type pair (see Inference Rules), but they can be overridden.
Fix: Use an explicit pj/lay-* function. For example, a categorical x with a numerical y defaults to a boxplot:
(-> (rdatasets/datasets-iris)
(pj/pose :species :sepal-width))Use pj/lay-point if you want the individual points instead:
(-> (rdatasets/datasets-iris)
(pj/lay-point :species :sepal-width))Numeric IDs Treated as Continuous Color
Symptom: You color by a subject/group ID column that contains numbers (e.g., 1, 2, 3), but instead of discrete colored groups you get a single continuous gradient.
Cause: The inference system sees a numeric column and treats it as continuous. Continuous color means no grouping – all data stays in one group with a gradient legend.
(def subject-scores
{:day [1 2 3 4 1 2 3 4 1 2 3 4]
:score [3 5 4 6 6 7 5 8 8 9 7 10]
:subject [1 1 1 1 2 2 2 2 3 3 3 3]})Gradient (wrong for IDs) – one line for the whole dataset, with the color sampled from the gradient legend. Plotje also prints a warning at the REPL pointing at the fix:
(-> subject-scores
(pj/lay-line :day :score {:color :subject}))Fix: Add :color-type :categorical to override the inference – three discrete groups, one line per subject:
(-> subject-scores
(pj/lay-line :day :score {:color :subject :color-type :categorical}))See Inference Rules for the full mechanism.
Numeric Column Treated as Continuous Instead of Categorical
Symptom: A column of discrete numbers (hour of day, year, subject ID) is treated as a continuous axis. A categorical-axis mark like :boxplot, :violin, or :lollipop rejects it with an error like "requires a categorical column". (pj/lay-bar does not error on a numeric axis – it draws a bar at each numeric position; use the same fix below to get evenly-spaced bands instead.)
Cause: The column contains numbers, so column-type inference classifies it as :numerical. These marks need :categorical.
A boxplot keyed by hour runs into this – :hour looks like integers, so it is inferred numerical:
(try
(-> {:hour [9 9 10 10 11 11] :value [1 2 3 4 5 6]}
(pj/lay-boxplot :hour :value)
pj/plan)
(catch clojure.lang.ExceptionInfo e (ex-message e)))"lay-boxplot requires a categorical column on either :x or :y, but both :hour and :value are numerical. Override with {:x-type :categorical} or {:y-type :categorical} to treat a numeric column as categorical."Fix: Add :x-type :categorical (or :y-type :categorical for horizontal layouts) to override the inferred type. No need to convert the column itself:
(-> {:hour [9 9 10 10 11 11] :value [1 2 3 4 5 6]}
(pj/lay-boxplot :hour :value {:x-type :categorical}))The override propagates into infer-column-types, so every downstream step (scale type, tick placement, domain) treats :hour as categorical. The same switch works for :y-type when a numeric column is on the y axis of a horizontal boxplot or similar layout. See Inference Rules for a worked example.
Nudge on a Categorical Axis
Symptom: Passing :nudge-x (or :nudge-y) to a label or point layer whose corresponding axis is categorical raises an error like ":nudge-x is a data-space shift and does not apply to a categorical x axis".
Cause: :nudge-x/:nudge-y shift coordinates by a data-space amount. On a categorical axis the coordinates are still category labels at this stage – their drawing positions are assigned later by the renderer – so a numeric shift has no defined meaning. A value label on a bar runs into this, because the bar’s axis is categorical:
(try
(-> {:species ["setosa" "versicolor" "virginica"] :pct [33.3 33.3 33.3]}
(pj/lay-bar :species :pct)
(pj/lay-text :species :pct {:text :pct :nudge-x -2})
pj/plan)
(catch clojure.lang.ExceptionInfo e (ex-message e)))":nudge-x is a data-space shift and does not apply to a categorical x axis. To place a label on a categorical axis use :align-x; to offset overlapping marks use :jitter or :position :dodge."Fix: To place a label on a categorical axis, anchor it with :align-x/:align-y instead – :align-x :right tucks the label inside a bar’s end. (To spread overlapping marks on a categorical axis, use :jitter or :position :dodge.)
(-> {:species ["setosa" "versicolor" "virginica"] :pct [33.3 33.3 33.3]}
(pj/lay-bar :species :pct {:color "#a6cee3"})
(pj/lay-text :species :pct {:text :pct :align-x :right})
(pj/coord :flip))Anchoring is covered in the Text and Label Placement section of Customization. :nudge-x and :nudge-y remain available on numeric and temporal axes.
Log Scale via :scale-x / :scale-y Options
Symptom: Passing {:scale-x :log} (or {:scale-y :log}) to a layer or to pj/options prints a warning – "does not recognize option(s): [:scale-x]" – and the chart comes out on a linear axis.
Cause: Scales are plot-level, not layer-level or option-map keys. They are set by the pj/scale function, not by a :scale-* key.
The wrong form does not throw; it warns and silently falls back to a linear axis:
(with-out-str
(-> (rdatasets/ggplot2-diamonds)
(pj/lay-point :carat :price {:scale-y :log})
pj/plan))"Warning: lay-point does not recognize option(s): [:scale-y]. Accepted: [:alpha :color :color-type :data :group :jitter :mark :nudge-x :nudge-y :position :shape :size :stat :text :x :x-type :y :y-type]\n"Fix: Use pj/scale:
(-> (rdatasets/ggplot2-diamonds)
(pj/lay-point :carat :price {:alpha 0.1})
(pj/scale :y :log))