13
$\begingroup$

I have a ragged Association of Association, say :

assoc = Association[
  "1" -> Association["a" -> "x", "b" -> "y"], 
  "2" -> Association[            "b" -> "z", "c" -> "k"]
                   ]  

I would like to transform it into a Association where level 1 and level 2 keys are reversed, that is to say :

Association[
      "a" -> Association["1" -> "x"], 
      "b" -> Association["1" -> "y", "2" -> "z"],
      "c" -> Association[            "2" -> "k"]
           ]

My solution is :

keysExplodedList = Reap[MapIndexed[Sow[Reverse[#2] -> #1] &, assoc, {2}]][[2, 1]]
groupedLevel1 = GroupBy[#[[1, 1]] &] @ keysExplodedList
groupedLevel2 = GroupBy[#[[1, 2]] &] /@ groupedLevel1
result = Map[#[[1, 2]] &, groupedLevel2, {2}]

<|"a" -> <|"1" -> "x"|>, "b" -> <|"1" -> "y", "2" -> "z"|>, "c" -> <|"2" -> "k"|>|>

Is there something more elegant ?

$\endgroup$
2
  • $\begingroup$ @WReach I have Mathematica 10.0.1 $\endgroup$
    – andre314
    Jun 21, 2015 at 14:55
  • $\begingroup$ My solution could be more concisely rewritten as : (Reap[MapIndexed[Sow[#2 -> #1] &, assoc, {2}]][[2, 1]] // GroupBy[{#[[1, 2]] &, #[[1, 1]] &}])[[All, All, 1, 2]] $\endgroup$
    – andre314
    Jun 22, 2015 at 16:16

5 Answers 5

16
$\begingroup$

Mathematica 10.1 almost supports this operation directly:

assoc // Query[Transpose]

(*
<| "a" -> <|"1" -> "x", "2" -> Missing["KeyAbsent", "a"]|>,
   "b" -> <|"1" -> "y", "2" -> "z"|>,
   "c" -> <|"1" -> Missing["KeyAbsent", "c"], "2" -> "k"|>
|>
*)

All that remains is to delete the unwanted Missing elements:

assoc // Query[Transpose] // DeleteMissing[#, 2]&

(*
<| "a" -> <|"1" -> "x"|>,
   "b" -> <|"1" -> "y", "2" -> "z"|>, 
   "c" -> <|"2" -> "k"|>
|>
*)

We can see that Query uses the undocumented function GeneralUtilities`AssociationTranspose to do the heavy-lifting:

Query[Transpose] // Normal

(* GeneralUtilities`AssociationTranspose *)

assoc // GeneralUtilities`AssociationTranspose

(*
<| "a" -> <|"1" -> "x", "2" -> Missing["KeyAbsent", "a"]|>,
   "b" -> <|"1" -> "y", "2" -> "z"|>,
   "c" -> <|"1" -> Missing["KeyAbsent", "c"], "2" -> "k"|>
|>
*)

An Imperative Solution

The words "elegant" and "imperative" rarely appear together these days, but an imperative solution can express the transposition directly:

Module[{r = <| |>}
, Do[r = Merge[{r, <| j -> <| i -> assoc[[i, j]] |> |>}, Association]
  , {i, Keys[assoc]}
  , {j, Keys[assoc[[i]]]}
  ]
; r
]

(*
<| "a" -> <|"1" -> "x"|>,
   "b" -> <|"1" -> "y", "2" -> "z"|>, 
   "c" -> <|"2" -> "k"|>
|>
*)

A ScanIndexed operator would come in handy here (the undocumented one in GeneralUtilities` is not, well, general enough).

$\endgroup$
4
  • 2
    $\begingroup$ A little less typing: DeleteMissing[Query[Transpose] @ assoc, 2] $\endgroup$
    – m_goldberg
    Jun 21, 2015 at 15:25
  • $\begingroup$ @m_goldberg Never thought of DeleteMissing for the DeleteCases... updated and thanks. $\endgroup$
    – WReach
    Jun 21, 2015 at 15:49
  • $\begingroup$ //Query[Transpose] is very interesting if Transpose can be used in its general form : Transpose[#,{n1,n2,n3...}]& (when there are more than 2 levels of Association). I suppose it is the case, but it is not documented. $\endgroup$
    – andre314
    Jun 22, 2015 at 18:33
  • 1
    $\begingroup$ @andre Alas, the general form of Transpose does not presently work on associations -- whether specified directly or after being interpreted by Query. $\endgroup$
    – WReach
    Jun 22, 2015 at 18:43
4
$\begingroup$

Not as nice as what WReach posted but this is what I came up with:

GroupBy[
  Join @@ Thread /@ Normal //@ assoc,
  {First@*Last, First}
][[All, All, 1, 2, 2]]
<|"a" -> <|"1" -> "x"|>, "b" -> <|"1" -> "y", "2" -> "z"|>, "c" -> <|"2" -> "k"|>|>
$\endgroup$
3
$\begingroup$

Here is another version, using KeyValueMap (needs 10.1):

regroup = 
  Composition[
    Map[Association],
    GroupBy[First -> Last],
    Flatten,
    Normal,
    KeyValueMap[Function[{k, v}, Map[k -> # &, v]]]
  ];

So that

regroup @ assoc

(* <|"a" -> <|"1" -> "x"|>, "b" -> <|"1" -> "y", "2" -> "z"|>, "c" -> <|"2" -> "k"|>|> *)
$\endgroup$
1
$\begingroup$

Just for completeness:

assoc // Query[ Transpose /* Map[ DeleteMissing ] ]

<|"a" -> <|"1" -> "x"|>, "b" -> <|"1" -> "y", "2" -> "z"|>, "c" -> <|"2" -> "k"|>|>

$\endgroup$
1
$\begingroup$
DeleteMissing /@ GeneralUtilities`AssociationTranspose[assoc]

<|a-><|1->x|>,b-><|1->y,2->z|>,c-><|2->k|>|>


I have not noted WReach have mentioned this method here.But I think this is a bug behavior in function Transpose when it process Association.And I have report it to Wolfram Research before this.And there is workaround to avoid this bug

DeleteMissing /@ Normal[Transpose[Dataset[assoc]]]

<|a-><|1->x|>,b-><|1->y,2->z|>,c-><|2->k|>|>

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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