Repository navigation
v2.0.9
A post-alpha audit pass reconciling the library with data.table-style semantics, plus a handful of correctness fixes uncovered by REPL verification of the DSL's per-partition execution paths.
Changed
qtilenow uses per-partition breakpoints when combined with exact keys in:by. Previously:by [:date (qtile :mktcap 5)]computed breakpoints once from the whole dataset (and silently produced wrong answers for per-date cross-sectional sorts — the canonical CRSP / Fama-French size sort). It now partitions by the exact keys first, then resolvesqtileagainst each sub-dataset, matching data.table / dplyr / q:An audit of every other DSL feature (;; Per-date size quintiles — now works the obvious way (core/dt stocks :by [:date (core/qtile :mktcap 5)] :agg {:mean-ret #dt/e (mn :ret)}) ;; Per-date NYSE-style breakpoints applied to all stocks (Fama-French size sort) (core/dt stocks :by [:date (core/qtile :mktcap 5 :from #dt/e (= :exchcd 1))] :agg {:mean-ret #dt/e (mn :ret)})
stat/*, aggregations inside composite#dt/e,cut :from,win/*,row/*,xbar,join :asof/:window) confirmedqtilewas the only outlier — every other feature already ran per-partition by virtue of living insideapply-group-*. Pure-qtile:by(no exact keys) still resolves globally since there is nothing to partition by.
Added
-
cast— long→wide reshaping. Complement tomelt. For each unique combination of:idcolumn values, pivots the:fromcolumn's distinct values into new columns filled from the:valuecolumn. New column names derived from:fromvalues (keywords pass through; strings converted viakeyword). Supports:aggfor duplicate cells and:fillfor missing cells (default nil).melt/castround-trip correctly.;; Reverse a melt (-> ds (melt {:id [:species :year] :measure [:mass :flipper]}) (cast {:id [:species :year] :from :variable :value :value})) ;; With aggregation for duplicate (id, from) cells (cast ds {:id [:date :sym] :from :metric :value :val :agg dfn/mean})
-
castaccepts a single-keyword:id. Normalised to a one-element vector, matchingmelt. Previously a single-keyword:iderrored with"Don't know how to create ISeq from: clojure.lang.Keyword".
Fixed
-
row/sum,row/mean,row/min,row/maxon all-nil rows. The four row-wise aggregators declared:float64readers but claimed in their docstrings to returnnilwhen every input is nil. Primitive float readers cannot holdnil— the value was silently coerced toNaN, contradicting the docstring. Readers are now:object, so all-nil rows honestly returnnil.row-min/row-maxalso cast non-nil results todoubleto preserve the always-numeric-result convention. -
wavg/wsumwith mismatched column lengths. Previously silently truncated (when the weight column was shorter) or NPE'd (when the value column was shorter). Now throws a structured:unequal-column-lengthsex-infowith:dt/op,:dt/weight-length, and:dt/value-lengthinex-data. -
join :asof :toleranceon datetime asof keys. Previously produced a rawjava.lang.ClassCastExceptionfrom an unguarded(double dt-value)coercion insidewithin-tolerance?. Now validates numerically-compatible asof key types upfront and throws a structured:join-tolerance-non-numericex-infowith actionable guidance (convert to epoch-milliseconds). Symmetric and asymmetric join key shapes (:onvs:left-on/:right-on) both report the correct column names inex-data. -
describeon all-missing numeric columns. When every value in a numeric column was missing,dfn/standard-deviationreturned-0.0whilemean,min,max,median, and percentiles correctly returnednil, producing an incoherent summary row.describe-columnnow routes all-missing numeric columns through the nil-filled branch (same as non-numeric columns). -
parse-window-specnow strict-validates window spec shape. Previously the implementation destructured[a b c :as wspec]and silently dropped any trailing elements. Malformed specs now throw a structured:join-invalid-windowex-info— trailing junk, wrong arity, non-numeric endpoints, non-vector specs, and misplaced unit keywords are all rejected upfront. Valid[lo hi],[lo hi unit], and[lo unit hi]shapes behave exactly as before. -
count-distinctnow excludesnil. The fn includednilin its distinct count, contradicting its docstring ("non-nil values"). Fixed by filteringsome?beforedistinct. -
qtileandcutnow use the same breakpoint algorithm.qtile'spercentile-breakpointspreviously used a floor-index approximation that produced different breakpoints thancut-bucket(which usesdfn/percentiles). The two now sharedfn/percentiles, soqtile :mktcap 5and#dt/e (cut :mktcap 5)produce identical bins for the same population. -
Breakpoint-at-exact-value semantics unified across
qtileandcut.bin-via-breakpointsnow uses<=(values equal to a breakpoint go to the lower bin), matchingcut-bucket'sjava.util.Arrays/binarySearchexact-match behaviour. The previously passingqtile-from-basictest's assertion was wrong; corrected to reflect actual semantics. -
xbar/xbar-bucketnow useMath/floorDiv. Previously usedquot, which truncates toward zero — so negative values bucketed incorrectly relative to q'sxbarsemantics. E.g.(xbar -3 5)now returns-5rather than0. -
validate-expr-colsandvalidate-select-colsNPE on zero-column datasets. Both helpers computed(->> avail-names (map ...) (sort-by second) first), which returnednilwhenavail-nameswas empty.(second nil)yieldednil, and(<= nil 3)threwNullPointerException. Guarded with(and closest ...)in the suggestion-emission branch.
Developer experience
win/scanop normalisation mirrorswin/each-prior. The parser now preferssym->opfor the canonical keyword, so invalid scan ops like/resolve to:div(consistent with the rest of the codebase) rather than a keyword literally spelled with a slash. Valid scan ops (+,*,max,min) are unchanged in both AST and runtime.
Internal
-
Damerau-Levenshtein deduplication. The edit-distance implementation used by typo suggestions was duplicated byte-for-byte in
datajure.coreanddatajure.expr. Extracted to the publicdatajure.expr/damerau-levenshteinas the single source of truth; bothvalidate-expr-cols/validate-select-colsandsuggest-opnow call it. -
Dead
win-opsset removed fromdatajure.expr(unused, and stale — missingwin/scanandwin/each-prior).
Testing
- Test count: 310 → 318 (+8 new deftests, +89 assertions). CI subset: 268/901 → 276/989. All passing.
- New deftests:
row-fns-all-nil-returns-nil-not-nan,wavg-wsum-unequal-lengths,asof-tolerance-non-numeric-error-test,describe-all-missing-numeric,wjoin-invalid-window-shape-test,qtile-per-group-breakpoints,qtile-from-with-exact-key. The existingqtile-combined-with-keywordtest was strengthened from a column-names-only check to an assertion of per-group bin counts (would fail loudly against the old global-breakpoint implementation).