10
$\begingroup$

Suppose that I have a this list of associations

   data = {<|"axis" -> x, "model" -> a, "p1" -> 1, "p2" -> 2|>,
           <|"axis" -> x, "model" -> b, "p1" -> 3, "p2" -> 4|>,
           <|"axis" -> y, "model" -> a, "p1" -> 5, "p2" -> 6|>,
           <|"axis" -> y, "model" -> b, "p1" -> 7, "p2" -> 8|>}

and I group the data into a nested association form:

grouped = GroupBy[data, 
                  {Key["axis"] -> KeyDrop["axis"],
                   Key["model"] -> KeyDrop["model"]},
                  First]

<|x -> <|a -> <|"p1" -> 1, "p2" -> 2|>,
         b -> <|"p1" -> 3, "p2" -> 4|> |>, 
  y -> <|a -> <|"p1" -> 5, "p2" -> 6|>,
         b -> <|"p1" -> 7, "p2" -> 8|> |> |>

How could you reverse this operation?

I have found a way to reverse it but it is very convoluted:

grouped // 
   AssociationMap[
    Function[{rule1}, 
     Keys[rule1] -> (AssociationMap[
        Function[{rule2}, 
         Keys[rule2] -> 
          Prepend[Values@rule2, {"axis" -> Keys[rule1], 
            "model" -> Keys[rule2]}]], Values[rule1]])]] //
  Query[Values, Values] //
 Flatten

I am looking for a more readable way to do this. Ideally one would use something like MatAt but as far as I know one cannot access values from higher levels on nested associations like this.

$\endgroup$
13
$\begingroup$

This looks a little simpler:

Catenate @ MapIndexed[
  Append[#,Thread[{"axis", "model"} -> Replace[#2, Key[x_] :> x, {1}] ]] &, 
  grouped, 
  {2}
]

(* 
   {
     <|"p1" -> 1, "p2" -> 2, "axis" -> x, "model" -> a|>, 
     <|"p1" -> 3, "p2" -> 4, "axis" -> x, "model" -> b|>, 
     <|"p1" -> 5, "p2" -> 6, "axis" -> y, "model" -> a|>, 
     <|"p1" -> 7, "p2" -> 8, "axis" -> y, "model" -> b|>
   }
*)

I didn't care about the order of keys, but if it matters, it can be easily fixed too.

EDIT

Generalization by WReach:

ungroup[keys_][assocs_] := 
  With[{l = {Length@keys}}, 
    Level[
      MapIndexed[<|Thread[keys -> #2[[All, 1]]], #|> &, assocs, l], 
      l
    ]
  ]

This can be used as:

ungroup[{"axis", "model"}][grouped]

And in this particular case, yields the same result, but can be used also for more (or less) deep nesting.

END EDIT

$\endgroup$
  • 5
    $\begingroup$ +1 Clever use of Catenate to strip two levels of keys. Should the need arise to ungroup three or more levels, we could generalize using Level, something like ungroup[keys_][assocs_] := With[{l = {Length@keys}}, Level[MapIndexed[<| Thread[keys -> #2[[All, 1]]], # |> &, assocs, l], l]]. $\endgroup$ – WReach Oct 19 at 14:32
  • $\begingroup$ @WReach Re : generalization - exactly. This crossed my mind, but I did not bother to do this for now :). Edited your code in. $\endgroup$ – Leonid Shifrin Oct 19 at 15:25
  • $\begingroup$ 100k looks good by your name :-) $\endgroup$ – Mr.Wizard Oct 20 at 6:06
  • $\begingroup$ @Mr.Wizard If you say so :) You have joined this club first, long time ago. $\endgroup$ – Leonid Shifrin Oct 20 at 8:44
  • $\begingroup$ @LeonidShifrin Thank you for your answer an congratulations on reaching 100k! $\endgroup$ – Gustavo Delfino Oct 21 at 15:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.