27  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))
sepal widthspeciessetosaversicolorvirginica2.02.53.03.54.04.5

Use pj/lay-point if you want the individual points instead:

(-> (rdatasets/datasets-iris)
    (pj/lay-point :species :sepal-width))
sepal widthspeciessetosaversicolorvirginica2.02.53.03.54.04.5

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}))
scoredaysubject131234345678910

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}))
scoredaysubject1231234345678910

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 :hour is numerical and :value is 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}))
valuehour91011123456

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 move a mark by a distance on the page use :offset-x, which works on any axis; to place a label relative to its point use :align-x; to spread overlapping marks use :jitter or :position :dodge."

Fix: To move a mark by a distance on the page, use :offset-x or :offset-y. These are drawing units applied after the scales, so they apply on a categorical axis as on any other:

(-> {:species ["setosa" "versicolor" "virginica"] :pct [33.3 33.3 33.3]}
    (pj/lay-bar :species :pct)
    (pj/lay-text :species :pct {:text :pct :align-x :center :offset-y -6}))
pctspecies33.333.333.3setosaversicolorvirginica051015202530

To place a label relative to its own point, anchor it with :align-x/:align-y: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))
speciespct33.333.333.3051015202530setosaversicolorvirginica

Anchoring is covered in Placing Marks. :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: :scale-x and :scale-y are not option keys at all. A scale is set with pj/scale, called on the pose, or inside a mapping written out in full – {:x {:column :carat :scale :log}}.

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].\n  Set by pj/scale, not by an options map: [:scale-y]\n  Accepted: [:alpha :color :color-type :data :group :in :jitter :mark :nudge-x :nudge-y :offset-x :offset-y :position :shape :size :stat :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))
pricecarat0.00.51.01.52.02.53.03.54.04.55.030050010002000300050001000020000

pj/scale takes the pose, an aesthetic – an axis (:x, :y) or a visual one (:size, :alpha, :color, :fill, :shape) – and either a type keyword (:linear, :log) or a scale specification map with :type and an optional :domain override. See the Inference Rules chapter for how scale types and domains interact with column inference.

x-Only Layer Types Do Not Accept a y Column

Symptom: "lay-histogram uses only the x column; do not pass a y column" error.

Cause: Histogram, density, and rug layer types use only the x column. Passing a y column is an error. (lay-bar is not among them – it uses a y column as the bar height when given one.)

(try
  (-> (rdatasets/datasets-iris)
      (pj/lay-histogram :sepal-length :sepal-width)
      pj/plan)
  (catch clojure.lang.ExceptionInfo e (ex-message e)))
"lay-histogram uses only the x column; do not pass a y column"

Fix: Remove the y column:

(-> (rdatasets/datasets-iris)
    (pj/lay-histogram :sepal-length))
sepal length4.55.05.56.06.57.07.58.00510152025

Categorical Column with Log Scale

Symptom: "Log scale requires numeric data" error.

Cause: Log scales only work with numerical columns. Categorical columns (strings, keywords) have no meaningful log transform.

(try
  (-> (rdatasets/datasets-iris)
      (pj/lay-bar :species)
      (pj/scale :x :log)
      pj/plot)
  (catch clojure.lang.ExceptionInfo e (ex-message e)))
"Log scale on :x requires numeric data, but column :species is non-numeric."

Fix: Use a numerical column for the log-scaled axis, or drop the log scale on the categorical axis.

Polar Coordinates with Unsupported Marks

Symptom: "Mark :line is not supported with polar coordinates. Supported polar marks: (:bar :point :rect :rug :text)" (or the same message for :area and other unsupported marks).

Cause: Polar coordinates currently support a subset of marks: :bar, :point, :rect, :rug, and :text. Layer types built on these marks (such as :histogram, which renders as bars, and :bar with a y column, which renders as rectangles) work too.

(try
  (-> {:x [1 2 3 4 5] :y [2 4 3 5 4]}
      (pj/lay-line :x :y)
      (pj/coord :polar)
      pj/plan)
  (catch clojure.lang.ExceptionInfo e (ex-message e)))
"Mark :line is not supported with polar coordinates. Supported polar marks: (:bar :point :rect :rug :text)"

Fix for now: Use a supported mark. A bar chart flipped to polar becomes a rose chart:

(-> (rdatasets/datasets-chickwts)
    (pj/pose :feed)
    pj/lay-bar
    (pj/coord :polar))

Support for :line, :area, and other marks in polar is planned. See the Polar Coordinates chapter for the full set of currently supported marks and examples.

Tooltip and Brush Not Working

Symptom: You set {:tooltip true} but no tooltip appears when hovering over points.

Cause: Tooltip and brush interactivity use JavaScript that requires a compatible notebook viewer. Static HTML export or some viewers may not support it.

Fix: Use Clay or another Kindly-compatible tool that supports kind/hiccup with embedded scripts.

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width {:color :species})
    (pj/options {:tooltip true}))
sepal widthsepal lengthspeciessetosaversicolorvirginica4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

Faceting Keys in a Layer’s Options Map

Symptom: An error like "Faceting is plot-level, not layer-level. Use (pj/facet pose col) ..." when you put :facet-col, :facet-row, :facet-x, or :facet-y inside a pj/lay-* options map.

Cause: Faceting configures the plot as a whole, not a single layer. Putting these keys in a layer’s options map is rejected with a guidance message.

(try
  (-> (rdatasets/datasets-iris)
      (pj/pose :sepal-length :sepal-width)
      (pj/lay-point {:facet-col :species})
      pj/plan)
  (catch clojure.lang.ExceptionInfo e (ex-message e)))
"Faceting is plot-level, not layer-level. Use (pj/facet pose col) or (pj/facet-grid pose col-col row-col) instead of putting facet-col in a layer's options map."

Fix: Use pj/facet (single-axis) or pj/facet-grid (two-axis) as a top-level step in the pipeline:

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width)
    (pj/facet :species))
sepal widthsepal length682.02.53.03.54.04.56868setosaversicolorvirginica

Constant :x or :y in a Layer’s Options

Symptom: a note is needed at one fixed spot, and no column holds the x and y it belongs at.

This is not an error. :x and :y may be given as a value, the same way a color may be "red" rather than a column. The layer below places its text at x 6.5 and y 3.5 with no dataset of its own, and a string :text on such a layer is the text itself rather than a column name:

(-> (rdatasets/datasets-iris)
    (pj/lay-point :sepal-length :sepal-width)
    (pj/lay-text {:x 6.5 :y 3.5 :text "mean"}))
sepal widthsepal lengthmean4.55.05.56.06.57.07.58.02.02.53.03.54.04.5

The other shape a value takes is beside a column. When :y is a column the layer describes the data, so the value given for :x repeats for every row – which is how a label at one fixed x is written. The five team names below line up at x 33 rather than sitting over their points, each at the revenue of its own row:

(-> {:team ["North" "South" "East" "West" "Central"]
     :spend [12 19 15 24 31]
     :revenue [30 45 38 62 74]}
    (pj/lay-point :spend :revenue)
    (pj/lay-text {:x 33 :y :revenue :text :team}))
revenuespendNorthSouthEastWestCentral12141618202224262830323430354045505560657075

:x and :y alone decide which of the two a value draws. A layer that gives both as values does not read the data at all, so it draws once; a value beside a column applies to each row, so it repeats. A string :text splits the same way: it is the text on a layer of values alone, and a column name once the layer has data to name a column in.

A value given for :x or :y is a data value like any other, so it takes part in the axis domains: a note placed beyond the data widens the axis to hold it. To place a mark on the panel instead – in drawing units from the corner of the panel background, leaving the axes alone – give the layer :in :drawing-area. The Placing Marks chapter covers that choice, and the two spaces the two readings belong to.

Reference lines remain their own layer types: pj/lay-rule-h with :y-intercept and pj/lay-rule-v with :x-intercept draw a line across the whole panel, which one :x and one :y cannot say.

A number is read against the data. A dataset built without column names is given integer ones, and a number in a mapping names such a column where the data carries it:

(-> (tc/dataset [[1 2] [3 4] [5 7]])
    (pj/lay-point 0 1))
Notestderr
[nREPL-session-edd5c5a2-d563-4d8e-8d14-18eac4801a3a] WARN tablecloth.api.dataset - Dataset creation behaviour changed for 2d 2-element arrays in v7.029. See https://github.com/scicloj/tablecloth/issues/142 for details.
1012345234567

Where the data carries no column of that name, the same number is a value to place a mark at, and the axis stretches to hold it. To say which reading you mean rather than letting the data decide, write the mapping in full: {:x {:column 0}} reads the column and {:x {:value 0}} places every mark at zero. Renaming the columns is the other remedy, and usually the clearer one:

(-> (tc/dataset [[1 2] [3 4] [5 7]])
    (tc/rename-columns [:x :y])
    (pj/lay-point :x :y))
Notestderr
[nREPL-session-edd5c5a2-d563-4d8e-8d14-18eac4801a3a] WARN tablecloth.api.dataset - Dataset creation behaviour changed for 2d 2-element arrays in v7.029. See https://github.com/scicloj/tablecloth/issues/142 for details.
yx12345234567

The Datasets chapter covers column names in full.

Dataset Missing Columns a Template References

Symptom: An error like "Cannot attach data: pose references column(s) [:group] not present in the dataset. Available columns: [:x :y]" when calling pj/with-data on a dataless template pose.

Cause: pj/with-data validates at attach time – every keyword column reference in the template must exist in the dataset, or the attachment fails immediately.

(def template
  (-> (pj/pose nil {:x :x :y :y :color :group})
      pj/lay-point))
(try
  (-> template
      (pj/with-data {:x [1 2 3] :y [4 5 6]}))
  (catch clojure.lang.ExceptionInfo e (ex-message e)))
"Cannot attach data: pose references column(s) [:group] not present in the dataset. Available columns: [:x :y]."

Fix: Either rename the dataset columns to match the template (tc/rename-columns), or adjust the template to reference the columns the dataset has.

(-> (pj/pose nil {:x :x :y :y})
    pj/lay-point
    (pj/with-data {:x [1 2 3] :y [4 5 6]}))
yx123456

Horizontal Ranking Bars Draw Biggest-at-Bottom

Symptom: A horizontal bar chart made with (pj/coord :flip) shows the first row of the data at the bottom of the chart. A descending-sorted “top-N” dataset ends up with the biggest bar at the bottom instead of the top.

Cause: coord :flip draws categories bottom-to-top in the order they appear in the data (matching ggplot2’s coord_flip()).

Descending data plotted as-is – “A” (the biggest) renders at the bottom, not the top:

(-> [{:category "A" :value 100}
     {:category "B" :value 50}
     {:category "C" :value 25}]
    (pj/lay-bar :category :value)
    (pj/coord :flip))
categoryvalue0102030405060708090100ABC

Fix for now: Sort the dataset ascending before plotting – the ascending order shows up top-to-bottom on the flipped axis, so the biggest value lands at the top:

(-> [{:category "A" :value 100}
     {:category "B" :value 50}
     {:category "C" :value 25}]
    (tc/dataset)
    (tc/order-by [:value] :asc)
    (pj/lay-bar :category :value)
    (pj/coord :flip))
categoryvalue0102030405060708090100CBA

A future opt-in option (e.g. (pj/coord :flip {:reverse-categorical true})) would remove the need to pre-sort. Tracked in CHANGELOG.md Known limitations.

Point Sizes Changed From an Earlier Release

Symptom: A plot with {:size :some-column} draws its middle values larger than it used to, and the difference between the smallest and largest points looks less dramatic.

Cause: A size scale spreads the square root of the value across the radii, so the area of a mark grows with the value rather than with its square. Earlier releases spread the value itself, which exaggerates the differences. The two ends of the range are unchanged; the values between them moved.

(-> {:x [1 2 3 4 5 6] :y [1 1 1 1 1 1] :n [1 4 9 16 25 36]}
    (pj/lay-point :x :y {:size :n}))
yxn5101520253035123456012

Fix: {:by :linear} restores the earlier reading, and {:by :area} gives the strict one, where equal steps in value are equal steps in ink:

(-> {:x [1 2 3 4 5 6] :y [1 1 1 1 1 1] :n [1 4 9 16 25 36]}
    (pj/lay-point :x :y {:size :n})
    (pj/scale :size {:by :linear}))
yxn5101520253035123456012

A :size or :alpha Column That Changes Nothing

Symptom: {:size :some-column} on a line, boxplot or lollipop draws marks of one size, and a warning names the marks that vary the aesthetic.

Cause: Only marks that draw a size per row can read a size column. Among the built-in marks that is pj/lay-point. Every other mark draws one width or one opacity for the whole layer, so the column changes nothing. No legend is drawn for it either, since the legend would describe an encoding the panel does not show.

Fix: Write the value itself for a layer-wide size – {:size 2} on a line is a stroke width – and map the column on a layer whose mark varies it.

(-> {:x [1 2 3] :y [2 4 3] :r [1 2 3]}
    (pj/pose :x :y)
    (pj/lay-line {:size 2})
    (pj/lay-point {:size :r}))
yxr123123234

Dodge Has No Effect on Point Layers

Symptom: Adding :position :dodge to pj/lay-point (or other non-bar marks) does not spread points apart by group – the plot looks identical to the version without :position :dodge.

Cause: :position :dodge is implemented for the bar mark (pj/lay-bar). On point/line/jitter and several other marks the option is accepted but silently ignored.

The two plans below produce identical x-coordinates for the rendered points – :position :dodge has no effect on points:

(def points-data
  {:x [1 1 2 2 3 3] :y [10 15 20 25 30 35] :group ["A" "B" "A" "B" "A" "B"]})
(defn point-xs [pose]
  (-> pose pj/plan :panels first :layers first :groups
      (->> (mapcat :xs) sort vec)))
(= (point-xs (-> points-data (pj/lay-point :x :y {:color :group})))
   (point-xs (-> points-data (pj/lay-point :x :y {:color :group :position :dodge}))))
true

Fix for now: For grouped categorical layouts use pj/lay-bar (counting with x only, or using a y column as height); dodge works there. To distinguish overlapping points by group on a numeric x, encode the group with :color, :shape, or pre-compute small offsets in the data. A proper dodge for points is tracked in CHANGELOG.md Known limitations.

(-> {:cat   ["A" "A" "B" "B" "C" "C"]
     :y     [10 20 30 40 50 60]
     :group ["a" "b" "a" "b" "a" "b"]}
    (pj/lay-bar :cat :y {:color :group :position :dodge}))
ycatgroupabABC0102030405060

Polar Bar Chart Has No Category Labels

Symptom: A bar chart flipped to polar ((pj/coord :polar)) renders as a rose chart, but no category text appears anywhere around the wedges.

Cause: Polar coord does not currently emit angular tick labels for bar-family marks – the underlying axis machinery places labels along Cartesian axes that polar replaces with a circular layout, and the equivalent angular ticks are not yet implemented.

The polar version shows the wedges sized by category, but the category names are absent:

(-> (rdatasets/datasets-chickwts)
    (pj/pose :feed)
    pj/lay-bar
    (pj/coord :polar))

Fix for now: Drop (pj/coord :polar) for the labeled view, or combine the polar plot with a separate Cartesian-coord version for the legend. A proper rose-chart label pass is tracked in CHANGELOG.md Known limitations.

(-> (rdatasets/datasets-chickwts)
    (pj/pose :feed)
    pj/lay-bar)
feedhorsebeanlinseedsoybeansunflowermeatmealcasein02468101214

Heatmap with Categorical Axes

Symptom: "class java.lang.String cannot be cast to class java.lang.Number" when passing a string column to pj/lay-tile.

Cause: pj/lay-tile (and the underlying :bin2d stat) requires numeric x and y columns – the tile boundaries are numeric intervals. Categorical axes are not yet supported for tile.

(try
  (-> {:x ["a" "b" "c"] :y ["a" "b" "c"] :v [1 2 3]}
      (pj/lay-tile :x :y {:fill :v})
      pj/plan)
  (catch Throwable t (.getMessage t)))
"class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')"

Fix: render a numeric-indexed grid (1-N integers in place of the categorical column) and pair :breaks with :tick-labels on the axis so the tick text shows the original category names:

(-> (for [day (range 1 8) hour (range 0 24)]
      {:day day :hour hour :v (+ (* 0.3 (Math/sin (* 0.5 hour)))
                                 (* 0.2 (mod day 3)))})
    (pj/lay-tile :day :hour {:fill :v})
    (pj/scale :x {:type :linear
                  :breaks [1 2 3 4 5 6 7]
                  :tick-labels ["Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"]}))
hourdayfill-0.2999970.699248MonTueWedThuFriSatSun05101520

If a true categorical axis (with binning over labels rather than numeric intervals) is what you need, that is tracked in CHANGELOG.md Known limitations. The integer-plus-:tick-labels pattern above covers most heatmap-with-categorical-axis cases.

Empty or All-Missing Column on a Grouping Layer

Symptom: "lay-boxplot requires a categorical column on either :x or :y, but :c has no rows and :v has no rows", or "has no values" where the column holds only nil.

Cause: The layer groups its data by category, and no row carries one. This is usually a data problem upstream of the plot – a filter that matched nothing, or a join that dropped every row:

(try
  (-> {:group [] :measurement []}
      (pj/lay-boxplot :group :measurement)
      pj/plot)
  (catch clojure.lang.ExceptionInfo e (ex-message e)))
"lay-boxplot requires a categorical column on either :x or :y, but :group has no rows and :measurement has no rows. A layer that groups by category needs at least one row carrying a category."

A column holding nothing but nil reports having no values, which separates the two cases – rows that never arrived, against rows that arrived empty:

(try
  (-> {:group [nil nil] :measurement [nil nil]}
      (pj/lay-boxplot :group :measurement)
      pj/plot)
  (catch clojure.lang.ExceptionInfo e (ex-message e)))
"lay-boxplot requires a categorical column on either :x or :y, but :group has no values and :measurement has no values. A layer that groups by category needs at least one row carrying a category."

The message names neither column a type, deliberately. An empty column and an all-nil one are both typed boolean by tech.ml.dataset, and Plotje reads such a column as numerical so the rest of the pipeline has something to work with – so a message naming the type would send you to check something that is not the problem.

:text Given a Value That Names No Column

Symptom: "Column :nope (from :text) not found in dataset", ending "It is not a label either -- a label is a string".

Cause: :text takes either a column reference, drawing one row’s value at each mark, or a literal string drawn at every mark. A keyword that names no column is neither:

(try
  (-> {:x [1 2] :y [1 2]}
      (pj/lay-text :x :y {:text :nope})
      pj/plot)
  (catch clojure.lang.ExceptionInfo e (ex-message e)))
"Column :nope (from :text) not found in dataset. Available: (:x :y) It is not a label either -- a label is a string."

Write a string for constant text – {:text "note"} – or name a column the dataset carries.

An Axis :domain That Is Not Two Numbers

Symptom: "pj/scale :y :domain [0] is not a pair of two finite numbers, as [0 100] is".

Cause: What a :domain means depends on the column it is read for. Against a continuous column it is the interval the panel spans, which is two finite numbers; against a categorical one it is the list of categories, in the order they are to be drawn. Since the column decides, the check is made once the column is known:

(try
  (-> {:height [1 2 3] :weight [1 2 3]}
      (pj/lay-point :height :weight)
      (pj/scale :y {:domain [0]})
      pj/plan)
  (catch clojure.lang.ExceptionInfo e (ex-message e)))
"pj/scale :y :domain [0] is not a pair of two finite numbers, as [0 100] is. :y here reads a continuous column, so its domain is the interval the panel spans. It has 1. To extend the interval the data gives rather than replace it, write :include."

Write both ends – {:domain [0 100]} – or, to extend the interval the data gives rather than replace it, write :include instead: {:include 0} puts zero on the axis and leaves the other end to the data. Scales works through the difference.

See Also

  • Core Concepts – the mapping and inference rules behind most of these symptoms

What’s Next

source: notebooks/plotje_book/troubleshooting.clj