Within the Plus
function Missing[...]
only acts as if it were zero. Actually, it is taken to be Sequence[]
. The result is:
Plus[10]
(* 10 *)
... which gives the same result as Plus[0, 10]
. The functions which are subject to this "sequence" behaviour are listed in an internal variable (as of v11.3):
Dataset; (* to trigger auto-loading *)
Dataset`Overrides`PackagePrivate`$MissingIsSequenceP
(* Plus|Min|Max|MinMax *)
There are other interesting lists as well:
Dataset`Overrides`PackagePrivate`$MissingIsIndeterminateP
(* Times | Minus | Power *)
Dataset`Overrides`PackagePrivate`$PreserveMissingP
(* Counts|CountsBy|GroupBy|Take|Drop|Select|...|TakeLargestBy|TakeSmallestBy *)
In the expression Boole[x > 1]
, it is the Greater
function (rather than Boole
) for which we desire special treatment. Alas, neither Greater
nor Boole
receive special treatment by MissingBehaviour
.
For fun, we could add Greater
to $MissingIsSequenceP
so that it gets the same behaviour as Plus
:
AppendTo[Dataset`Overrides`PackagePrivate`$MissingIsSequenceP, Greater]
So then:
as // Query[All, asF]
(* <| a -> <|class1 -> 11, class2 -> 0|>, b -> <|class1 -> 10, class2 -> 1|>|> *)
... but notice that the second "class2"
is 1
instead of 0
. This is because:
Greater[1]
(* True *)
If we always want an occurrence of Missing
to be zero, we can take matters into our own hands:
Block[{Missing = 0&}
, as // Query[All, asF, MissingBehavior -> None]
]
(* <| a -> <|class1 -> 11, class2 -> 0|>, b -> <|class1 -> 10, class2 -> 0|>|> *)
or, somewhat more selectively:
Block[{Missing}
, Missing /: (f:Plus|Greater)[a___, m__Missing, b___] := f[a, 0, b]
; as // Query[All, asF, MissingBehavior -> None]
]
Note that it was necessary to turn off MissingBehaviour
so that our overriding definitions of Missing
would take effect. This is a standard and supported way to make Missing
behave however we like.
Semi-unsanctioned Solution
If we want the standard MissingBehaviour -> Automatic
functionality to be supplemented with special rules for Boole
, we can get more aggressive:
Internal`InheritedBlock[{Boole}
, Unprotect[Boole]
; Boole[Except[True|False]] = 0
; as // Query[All, asF]
]
(* <| a -> <|class1 -> 11, class2 -> 0|>, b -> <|class1 -> 10, class2 -> 0|>|> *)
This is characterized as "semi-unsanctioned" because it uses Internal`InheritedBlock which is undocumented, but likely to remain around for a long time.
Fully unsanctioned solution
We could even go so far as to monkey-patch some internal definitions within the Query
subsystem. As of version 11.3, this would entail adding definitions as desired to this function:
Dataset;
Needs["GeneralUtilities`"]
PrintDefinitions[Dataset`Overrides`PackagePrivate`attachMissingUpvalues]
Or for query operators (not relevant to the present case):
PrintDefinitions[Dataset`ToMissingSafe]
However, these solutions are very brittle and likely to break in future versions. I would recommend against using them. Use the Block[{Missing}, ...]
pattern instead.