7

Related to this question, can functions be mapped to named Keys in ragged lists of Associations?

ds = {<|"a" -> 1, "b" -> 2|>, <|"b" -> 3, "c" -> 4|>}

This fails:

ds // Query[All, {"a" -> f}]
{<|"a" -> f[1], "b" -> 2|>, Missing["Failed"]}

As does this:

ds // MapAt[f, {All, "a"}]

MapAt::partw: Part {All,a} of {<|a->1,b->2|>,<|b->3,c->4|>} does not exist.

One strategy might be to normalize the list by inserting Missing values using KeyUnion

ds // KeyUnion // Query[All, {"a" -> f}]
{<|"a" -> f[1], "b" -> 2,    "c" -> Missing["KeyAbsent", "c"]|>, 
 <|"a"->     f[Missing["KeyAbsent", "a"]], "b" -> 3, "c" -> 4|>}

But in this case is there a way to ignore Missing? Perhaps using Position? I did not see a specific MissingBehavior option that works here.

EDIT

As pointed out here, FilterRules works in some cases but it's limited.

This is interesting as that's a v6 function intended for use with Options rather than the v10 Associations.

Here is a the operator version:

filterRules[expr_][as_Association] := Query[FilterRules[expr, as]][as];

Example:

{<|"a" -> 1, "b" -> 2|>, <|"a" -> 3|>, <|"b" -> 4, "c" -> 5|>} // 
 Query[All, filterRules@{"a" -> f, "b" -> g, "c" -> h}]
{<|"a" -> f[1], "b" -> g[2]|>, <|"a" -> f[3]|>, <|"b" -> g[4], "c" -> h[5]|>}

However, there are some fails when applied to Associations. If the Rules don't mention at least one Key in a given Association, it returns an empty Association - this is at odds with the Query[{key-> f}] semantics.

Dataset[{<|"a" -> 1, "b" -> 2|>, <|"b" -> 3, "c" -> 4|>}][All, 
 filterRules@{"a" -> f}]
{<|"a" -> f[1], "b" -> 2|>, <||>}

Also, doesn't seem to work on symbolic Keys:

Dataset[{<|"a" -> 1, False -> 2|>, <|False -> 3, "c" -> 4|>}][All, 
 filterRules@{"a" -> f, Key[False] -> g}]
{<|"a" -> f[1], False -> 2|>, <||>}

And - warning - crashes the kernel with composite Keys:

Dataset[{<|"a" -> 1, {"b1", "b2"} -> 2|>, <|{"b1", "b2"} -> 3, 
     "c" -> 4|>}][All, 
  filterRules@{"a" -> f, Key[{"b1", "b2"}] -> g}] // n
alancalvitti
  • 15,143
  • 3
  • 27
  • 92

2 Answers2

6
ds = {
   <|"a" -> 1, "b" -> 2|>, 
   <|"b" -> 3, "c" -> <|"a" -> 1, "g" -> 2|>|>
}

Not perfect but I'd probably use it:

ds // Query[
  All, 
  Replace[s : KeyValuePattern["a" -> _] :> MapAt[f, s, "a"]]
]
{
  <|"a" -> f[1], "b" -> 2|>, 
  <|"b" -> 3,  "c" -> <|"a" -> 1, "g" -> 2|>|>
}

Or

... :> <|s, "a" -> f @ s @ "a"|>

or

Query[
   All, 
   If[KeyExistsQ["a"][#], MapAt[f, "a"][#], #] &
]
Kuba
  • 136,707
  • 13
  • 279
  • 740
5

Rather hackish but perhaps still of use:

Quiet@*MapAt[f, "a"] /@ ds /. _MapAt[x_] :> x
{<|"a" -> f[1], "b" -> 2|>, <|"b" -> 3, "c" -> 4|>}

Responding to your update something like this might work:

filterRules[expr_][as_Association] := 
  as // Query[
     FilterRules[expr, Join[#, Key /@ #] & @ Keys @ as]
       // Replace[{} -> Identity]
   ]
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371