[WSS18] Spacing for arbitrarily grouped graphical elements

Posted 10 months ago
618 Views
|
0 Replies
|
1 Total Likes
|

Spacing for arbitrarily grouped graphical elements

Context

Before we go into too much depth, lets clarify that title.

What is "arbitrarily grouped"?

Arbitrarily grouped, as used here, is the nesting (via lists) of non-list elements in an unstructured way e.g. each list level can have different number of elements

For example

complexGrouping =
{ (* level 1: 8 elements *)
1, 1, 1,
{ (* level 2: 4 elements *)
2, 2,
{ (* level 3: 4 elements  *)
3, 3,
{ (* level 4: 3 elements *)
4, 4,
{ (* level 5: 2 elements *)
5,
{ (* level 6:2 elements *)
6,
{ (* level 7: 2 elements *)
7, 7
}
}, 5
}
}, 3
}, 2
}, 1, 1, 1,
{ (* level 2: 1 element *)
2
}
};


Suppose that complexGrouping is some form of data from a dataset grouped by a hierarchy of properties. One may wish to have, for example, a BarChart of this data where bars are spaced according to this complex nesting.

For example, calling BarChart[{1,2,3,4}] vs calling BarChart[{{1,2},{3,4}}]:

where the spacing between bars is level dependent.

Approach

Given a arbitrarily grouped list, we will need to traverse the list in a left-first traversal, which can be achieved via MapIndexed. This will let us keep track of both the absolute index (if the list were Flatten-ed, at what position the element occurs in) as well as the relative index (the index relative to the elements nearest enclosing list). These values may come useful later.

For simplicity, let's consider the BarChart and how bars should be positioned with arbitrary grouping. Clearly bars should be spaced in a level dependent manner; however, it is not as simple as just moving bar $i+1$ over by the spacer for the level in which bar $i$ is located.

Consider:

SpacerFunction[level_Integer]:= 1 / level;

{1, {2}, 3}


This example emphasizes that spacing should be applied with-in a level i.e. the space between the bars for Bar[1] and Bar[2] should be the same for Bar[2] and Bar[3]:

spacers = {1, 1}


So our function for {1, {2,2,2}, 1} should result in spacers {1, 1/2, 1/2, 1}.

However, these simple examples can be a bit misleading.

Now consider:

{
1, {{{{4,4}}}, {{{4,4}}}}
}


Using the above idea, it is unclear what spacer we should use. It makes stylistically, to use the lowest nesting accessible e.g. although the $3$ element, $4$ is in level $4$, the lowest nesting accessible (going left-to-right) is $2$ (4}}}).

Getting this value is tricky.

Demonstration

The attached .m file has the relevant code.

With  complexList = {1, 1, 1, {2, 2, {3, 3, {4, 4, {5, {6, {7, 7}}, 4}}, 3}, 2}, 1, 1,1};

arbSpace = ArbitrarilySpace[complexList]; 

The aggregated value tells us how to position (translate in either x or y direction) the current element.

So it is quite easy to make an arbitrary bar chart:

where BarChart would not be able to render anything:

Discussion

Should Mathematica allow for arbitrarily grouped data?

In my personal opinion the answer depends on whether or not Mathematica wants to be opinionated about user input.

Allowing for arbitrarily grouped data can allow users to insert and evaluate code that may make no sense.

On the other hand, sometimes nesting data only to level 2, is not always sufficient. For example, what if you wanted to split the example Titanic dataset up by survived then gender then class.

titanic = ExampleData[{"Dataset", "Titanic"}];

survivedClasses = DeleteDuplicates[Normal[titanic[All, "survived"]]]
sexClasses = DeleteDuplicates[Normal[titanic[All, "survived"]]]
classClasses = DeleteDuplicates[Normal[titanic[All, "class"]]]

res = Table[
N@Mean[
DeleteMissing[
Normal[
GroupBy[
GroupBy[
GroupBy[titanic,
"survived"][[sr]]
, "sex"][[sx]],
"class"][[c]]
[All, "age"]
]]]
, {sr, Length@ survivedClasses}, {sx, Length@ sexClasses}, {c,
Length@classClasses}];


BarChart won't be able to help here unless one uses Flatten[Partition[res, {3}]] (there are 3 classes), but then the visual indicator for data being related is lost.

While the examples above all dealt with BarChart, this function can be used to generalize almost any categorical based chart, e.g. BoxWhiskerChart.

Attachments: