2
$\begingroup$

I have a list of associations :

testdata = {<|"label" -> "v1", "value1" -> -2, "value2" -> 6|>,
<|"label" -> "v2", "value1" -> 4, "value2" -> -4|>,
<|"label" -> "v3", "value1" -> 6, "value2" -> -5|>};

The first element of each association is the X axis label, each following key indicates the set of data it belongs to.

Using Vitali's answer here I have managed to get ListPlot to use each first element as X axis labels, but the code isn't the cleanest due to repeated usage of "testdata"

ListPlot[{testdata[[All, 2]], testdata[[All, 3]]}, Joined -> True, 
Ticks -> {Transpose[{Range@Length@testdata[[All, 1]], 
testdata[[All, 1]]}], Automatic}, 
PlotLegends -> DeleteDuplicates@Keys@testdata[[All, 2 ;; 3]]]

Using key names is a tiny bit clearer at the expense of verbosity:

ListPlot[{testdata[[All, "value1"]], testdata[[All, "value2"]]}, 
Joined -> True, 
Ticks -> {Transpose[{Range@Length@testdata[[All, "label"]], 
testdata[[All, "label"]]}], Automatic}, 
PlotLegends -> DeleteDuplicates@Keys@testdata[[All, 2 ;; 3]]]

I believe there must be a way to substitute in a pure function for this usage but am struggling to find it - any ideas?

Note that Im trying to avoid explicit use of Dataset due to its somewhat quirky behaviour in 10.0.x

BTW

Named keys in associations doesn't seem to support the ;; span specification syntax of Part - is this a bug?

testdata[[All,2;;3]] (* works *)
testdata[[All,"value1";;"value2"]] (* doesnt work *)  
$\endgroup$
  • $\begingroup$ You mean something like ListPlot[testdata[[All,#]]&/@{ "value1","value2"}, Joined -> True, Ticks -> {Transpose[{Range@Length@#, #}]&@testdata[[All, "label"]], Automatic}, PlotLegends -> DeleteDuplicates @Keys @ testdata[[All, 2 ;; 3]]]? $\endgroup$ – kglr Jan 2 '15 at 19:37
  • $\begingroup$ @kguler Fairly close to what I was thinking, but was wondering if you can take the testdata completely outside of the main ListPlot expression completely, but somehow retain separate parts inside? Im probably trying to push things too far :) $\endgroup$ – Gordon Coale Jan 2 '15 at 21:18
  • $\begingroup$ maybe ListPlot[{#["value1"]&/@#,#["value2"]&/@#}, Joined -> True, Ticks -> {Transpose[{Range@Length@#, #}]&[#["label"]&/@#], Automatic}, PlotLegends -> DeleteDuplicates @Keys[#][[All,2;;]]]&@testdata? $\endgroup$ – kglr Jan 2 '15 at 21:39
2
$\begingroup$

We can separate the data element selection from the ListPlot generation as follows:

testdata[[All, {"label", "value1", "value2"}]] //
ListPlot[
  Transpose @ Values @ #[[All, 2;;]]
, Joined -> True
, Ticks -> {MapIndexed[{#2[[1]], #}&, #[[All, 1]]], Automatic}
, PlotLegends -> DeleteDuplicates @ Keys @ #[[All, 2;;]]
]&

plot screenshot

Note the use of a list of keys in testdata[[All, {"label", "value1", "value2"}]]. It is not legal to use key names in spans since, in general, keys are not guaranteed to be in the same order within a list of associations.

The pure function is set up to assume that the first data element is the label and that the remaining data elements are to be plotted. This allows us to use the expression for other association lists where the keys are different, and appear in jumbled order:

testdata2 =
  { <|"x1" -> -10, "x2" -> 30, "title" -> "v1"|>
  , <|"x1" -> 20, "title" -> "v2", "x2" -> 22|>
  , <|"x2" -> 20, "x1" -> 30, "title" -> "v3"|>
  };

testdata2[[All, {"title", "x1", "x2"}]] //
ListPlot[
  Transpose @ Values @ #[[All, 2;;]]
, Joined -> True
, Ticks -> {MapIndexed[{#2[[1]], #}&, #[[All, 1]]], Automatic}
, PlotLegends -> DeleteDuplicates @ Keys @ #[[All, 2;;]]
]&

plot screenshot

It is inconvenient to copy-and-paste that long pure function expression each time, so we might consider packaging it up for reuse:

myListPlot[data_] :=
  ListPlot[
    Transpose @ Values @ data[[All, 2;;]]
  , Joined -> True
  , Ticks -> {MapIndexed[{#2[[1]], #}&, data[[All, 1]]], Automatic}
  , PlotLegends -> DeleteDuplicates @ Keys @ data[[All, 2;;]]
  ]

testdata[[All, {"label", "value1", "value2"}]] // myListPlot

plot screenshot

testdata2[[All, {"title", "x1", "x2"}]] // myListPlot

plot screenshot

Alternatively, we could have the caller explicitly specify the label and data key names:

myListPlot2[data_, labelKey_, {dataKeys__}] :=
  data[[All, {labelKey, dataKeys}]] // myListPlot

myListPlot2[testdata2, "title", {"x1", "x2"}]

plot screenshot

$\endgroup$
  • $\begingroup$ Lovely - exactly what I had in mind. Still digesting kgulers answer below before marking as answered. $\endgroup$ – Gordon Coale Jan 3 '15 at 12:39
  • $\begingroup$ Lovely answer: addresses all my issues (including the span question) and also contains a logical extrapolation of where I wanted to go. $\endgroup$ – Gordon Coale Jan 5 '15 at 14:57
1
$\begingroup$

You can also do:

ClearAll[listplotF];
listplotF[opts_:{Joined->True}]:= Module[{t, d = Join @@ Through @ 
            {Composition[Transpose, Values], Composition[DeleteDuplicates, Rest/@#&, Keys]}@#}, 
   ListPlot[{#2,#3}, Ticks->(t={Thread[{Range@Length@#,#}],Automatic}),
            FrameTicks->t,
            PlotLegends -> #4, Sequence@@opts]&@@d]&;

listplotF[]@testdata

enter image description here

listplotF[{Joined->True,PlotTheme->"Marketing"}]@testdata

enter image description here

$\endgroup$
  • $\begingroup$ Several new functions to me in that - thanks. $\endgroup$ – Gordon Coale Jan 5 '15 at 14:58

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.