-
Notifications
You must be signed in to change notification settings - Fork 51
fix: null handling and numeric correctness in statistics, metrics and parsing #214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
74ae891
cbefe71
8fbe69f
1aa3329
bd873cc
321b3c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -209,6 +209,28 @@ columnBitmap (UnboxedColumn bm _) = bm | |
| columnBitmap (PackedText bm _) = bm | ||
| columnBitmap (MergedColumn _ _) = Nothing | ||
|
|
||
| {- | Drops the null values in a nullable column: those slots hold a sentinel, | ||
| not a value. Identity on columns without a bitmap. 'VG.ifilter' inspects only | ||
| the index, so boxed error thunks at null slots are never forced. | ||
| -} | ||
| dropNulls :: Column -> Column | ||
| dropNulls (BoxedColumn (Just bm) xs) = | ||
| BoxedColumn Nothing (VG.ifilter (\i _ -> bitmapTestBit bm i) xs) | ||
| dropNulls (UnboxedColumn (Just bm) xs) = | ||
| UnboxedColumn Nothing (VG.ifilter (\i _ -> bitmapTestBit bm i) xs) | ||
| dropNulls c@(PackedText (Just _) _) = dropNulls (materializePacked c) | ||
| dropNulls c = c | ||
| {-# INLINE dropNulls #-} | ||
|
|
||
| {- | 'dropNulls', unless the view type @a@ is @Maybe@-headed: a @Maybe@-typed | ||
| view encodes the nulls as values, so the column passes through untouched. | ||
| -} | ||
| dropNullsExceptMaybe :: forall a. (Typeable a) => Column -> Column | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is confusing. Why doesn't drop nulls handle this case. That would be a good thing to put in the comment. Let me read the calm site. |
||
| dropNullsExceptMaybe c = case typeRep @a of | ||
| App m _ | Just HRefl <- eqTypeRep m (typeRep @Maybe) -> c | ||
| _ -> dropNulls c | ||
| {-# INLINE dropNullsExceptMaybe #-} | ||
|
|
||
| {- | Decode a 'PackedText' into a @BoxedColumn Text@ (bit-identical to | ||
| materializing at freeze). Identity on every other column. | ||
| -} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,7 +73,9 @@ varianceStep (VarAcc !n !meanVal !m2) !x = | |
|
|
||
| computeVariance :: VarAcc -> Double | ||
| computeVariance (VarAcc !n _ !m2) | ||
| | n < 2 = 0 -- or error "variance of <2 samples" | ||
| | n == 0 = throw $ EmptyDataSetException "variance" | ||
| -- undefined at n = 1: NaN, not 0 | ||
| | n < 2 = 0 / 0 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Throw here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Variance' is also what the groupBy path calls (variance = Agg (CollectAgg "variance" variance') in Functions.hs), so a throw here kinda takes down a whole aggregation when any group is a singleton, and the scatter kernels would need to throw too to stay in sync. It's left as NaN for now, same question as the kernel thread.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool. Maybe to make this PR easier to reason about please add specific examples or behaviour changes in the PR description. Sort of like how a front end engineer adds screenshots to their PRs. A clear before and after of failure modes and their new fixes - they could even be copies of the test examples but they should be something like: Before: $ ./scripts/repl.sh
ghci> :script dataframe.ghci
dataframe> df <- D.readCsv "./data/housing.csv"
dataframe> -- the operationThen after would paste a similar thing.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added before/after examples to the description. |
||
| | otherwise = m2 / fromIntegral (n - 1) | ||
| {-# INLINE computeVariance #-} | ||
|
|
||
|
|
@@ -106,7 +108,7 @@ skewnessStep (SkewAcc !n !meanVal !m2 !m3) !x' = | |
| computeSkewness :: SkewAcc -> Double | ||
| computeSkewness (SkewAcc n _ m2 m3) | ||
| | n < 3 = 0 -- or error "skewness of <3 samples" | ||
| | otherwise = (sqrt (fromIntegral n - 1) * m3) / sqrt (m2 ^ (3 :: Int)) | ||
| | otherwise = (sqrt (fromIntegral n) * m3) / sqrt (m2 ^ (3 :: Int)) | ||
| {-# INLINE computeSkewness #-} | ||
|
|
||
| skewness' :: (VU.Unbox a, Real a, Num a) => VU.Vector a -> Double | ||
|
|
@@ -202,11 +204,14 @@ interQuartileRange' samp = | |
| {-# INLINE interQuartileRange' #-} | ||
|
|
||
| meanSquaredError :: VU.Vector Double -> VU.Vector Double -> Maybe Double | ||
| meanSquaredError target prediction = | ||
| let | ||
| squareDiff = VU.ifoldl' (\sq i e -> (e - target VU.! i) ^ (2 :: Int) + sq) 0 prediction | ||
| in | ||
| Just $ squareDiff / fromIntegral (max (VU.length target) (VU.length prediction)) | ||
| meanSquaredError target prediction | ||
| | VU.length target /= VU.length prediction = Nothing | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great. |
||
| | VU.null target = Nothing | ||
| | otherwise = | ||
| Just | ||
| ( VU.sum (VU.zipWith (\t p -> (p - t) ^ (2 :: Int)) target prediction) | ||
| / fromIntegral (VU.length target) | ||
| ) | ||
| {-# INLINE meanSquaredError #-} | ||
|
|
||
| mutualInformationBinned :: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm. @daikonradish does it make sense to stick a NaN here or fail instead. I think the right thing is to make it optional but the ergonomics of dealing with
Maybemake me question putting it in the happy path.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the same code the groupBy interpreter path uses... so Ig throwing there means one singleton group kills the whole aggregation (and the kernels would have to match)? NaN keeps the singleton visible without killing the query, and matches pandas/numpy (sample variance of n=1 -> NaN). Can simply switch all three to throw... (if u want it to fail loud)