I use the following code to create a histogram of grades. Grades are provided as a List of strings such as "A-", "D+", "E" (don't ask), etc. The +/- substitution hack is necessary to sort the grades in my desired order.

f[s_String] := 
  If[StringLength@s == 1, s <> "b", 
   StringReplace[s, {"+" -> "a", "-" -> "c"}]];
g[s_String] := StringReplace[s, {"a" -> "+", "b" -> "", "c" -> "-"}];
  Tally@(f /@ {"C", "B-", "B", "B+", "B", "C-", "E", "B", "D", "D+", 
      "C-", "B", "C", "B-", "C+", "E", "B-", "B-", "C-", "A", "C", 
      "B-", "B", "C+", "B", "C", "B+", "C+", "D-", "C", "A", "E", "B",
       "B+", "C", "C", "D+", "D", "C", "C-", "C", "B", "D", "B", "B", 
      "E", "B", "W", "W"})
BarChart[Last /@ %, ChartLabels -> g /@ First /@ %]

(* {{"Wb", 2}, {"Eb", 4}, {"Dc", 1}, {"Db", 3}, {"Da", 2}, {"Cc", 
  4}, {"Cb", 9}, {"Ca", 3}, {"Bc", 5}, {"Bb", 11}, {"Ba", 3}, {"Ab", 
  2}} *)

enter image description here

I'm looking for a less hackish way to do this. Any suggestions?

  • $\begingroup$ ""E" (don't ask)" - that's as effective as saying "don't think of a polka-dotted elephant" ;) Less facetiously: the sorting order is B- < B < B+, correct? $\endgroup$ – J. M.'s technical difficulties Sep 28 '18 at 12:02
  • $\begingroup$ @J.M.issomewhatokay. Yes, you are correct with the sorting order. I was very surprised when moving to my new institution and realized that we don't award "F" grades here... $\endgroup$ – bobthechemist Sep 28 '18 at 13:11

This is a great use for the Association data structure, which makes so many tasks in Mathematica that much more pleasant.

First, we can just write out a ranking of grades:

ranking = {"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", 
  "D", "D-", "E", "W"};

Then we take your grades and count how many of each there are into an association with Counts:

grades = {"C", "B-", "B", "B+", "B", "C-", "E", "B", "D", "D+", "C-", 
  "B", "C", "B-", "C+", "E", "B-", "B-", "C-", "A", "C", "B-", "B", 
  "C+", "B", "C", "B+", "C+", "D-", "C", "A", "E", "B", "B+", "C", 
  "C", "D+", "D", "C", "C-", "C", "B", "D", "B", "B", "E", "B", "W", 

counts = Counts[grades]
(* <|"C" -> 9, "B-" -> 5, "B" -> 11, "B+" -> 3, "C-" -> 4, "E" -> 4, 
     "D" -> 3, "D+" -> 2, "C+" -> 3, "A" -> 2, "D-" -> 1, "W" -> 2|> *)

This is a table associating each grades with its counts, but it's in a useless order. Let's put it in the order you want, using KeyTake, which we can pass directly to BarChart, which can Automaticly figure out the labels:

ordered = KeyTake[Counts[grades], Reverse@ranking]
(* <|"W" -> 2, "E" -> 4, "D-" -> 1, "D" -> 3, "D+" -> 2, "C-" -> 4, 
     "C" -> 9, "C+" -> 3, "B-" -> 5, "B" -> 11, "B+" -> 3, "A" -> 2|> *)

BarChart[ordered, ChartLabels -> Automatic]

bar chart

| improve this answer | |
  • 1
    $\begingroup$ Neat use of Association[]! $\endgroup$ – J. M.'s technical difficulties Sep 28 '18 at 12:32
  • 1
    $\begingroup$ Sorry, I had a stale window that was open for a while, and did not see your answer ... $\endgroup$ – Szabolcs Sep 28 '18 at 12:42
  • $\begingroup$ @Szabolcs Haha jinx! $\endgroup$ – Pillsy Sep 28 '18 at 12:47
  • 1
    $\begingroup$ Very nice - I would not have come up with Counts and KeyTake on my own. $\endgroup$ – bobthechemist Sep 28 '18 at 13:13

I had to deal with similar problems many times, and I usually do this with associations.

We start by defining an exhaustive list of grades in our desired order. I am not that familiar with the American grading system, so I might have gotten some parts of this wrong.

order = {"W", "E-", "E", "E+", "D-", "D", "D+", "C-", "C", "C+", "B-","B", "B+", "A-", "A", "A+"};

We are going to use this to reorder all our datasets. You can produce this list manually or automatically, then re-use it multiple times. In the application that I had, automatic sorting was not possible at all.

Now create the histogram as an association. We try to always work with associations.

hist = Counts[grades]
(* <|"C" -> 9, "B-" -> 5, "B" -> 11, "B+" -> 3, "C-" -> 4, 
 "E" -> 4, "D" -> 3, "D+" -> 2, "C+" -> 3, "A" -> 2, "D-" -> 1, 
 "W" -> 2|> *)

If you only want to keep those grades that actually appear in the dataset, do

BarChart[KeyTake[hist, order], ChartLabels -> Automatic]

Mathematica graphics

If you also want to keep grades whose count is 0,

 Join[AssociationThread[order, 0], hist],
 ChartLabels -> Automatic

Mathematica graphics

An alternative that is often useful:

 Lookup[hist, order, 0],
 ChartLabels -> order

Mathematica graphics

| improve this answer | |
  • 2
    $\begingroup$ +1 for "Keep grades with counts of zero". (Although we don't award E+ at our institution - that's crazy talk) $\endgroup$ – bobthechemist Sep 28 '18 at 13:15

Let's write a custom ordering function that Sort[] can use:

gradeOrder[g1_, g2_] := Block[{ord},
     ord = -Order[StringTake[g1, 1], StringTake[g2, 1]];
     If[ord != 0, ord,
        Which[StringLength[g1] == 1, 2 Boole[StringMatchQ[g2, "*+"]] - 1,
              StringLength[g2] == 1, 2 Boole[StringMatchQ[g1, "*-"]] - 1,
              True, Order[StringTake[g1, {2}], StringTake[g2, {2}]]]]]

With this:

grades = {"C", "B-", "B", "B+", "B", "C-", "E", "B", "D", "D+", "C-", "B", "C", "B-",
          "C+", "E", "B-", "B-", "C-", "A", "C", "B-", "B", "C+", "B", "C", "B+", "C+",
          "D-", "C", "A", "E", "B", "B+", "C", "C", "D+", "D", "C", "C-", "C", "B", "D",
          "B", "B", "E", "B", "W", "W"};


{gl, nl} = Transpose[Tally[Sort[grades, gradeOrder]]];

BarChart[nl, ChartLabels -> gl]

grade chart

| improve this answer | |
  • $\begingroup$ As I was trying to refactor my code, I was looking in the documentation for examples of more sophisticated ordering functions to pass to Sort. $\endgroup$ – bobthechemist Sep 28 '18 at 13:17

This question has plenty of answers already, but here's a solution based on the rather useful function PositionIndex. Basically, you first specify the ordering of the grades and then use PositionIndex to construct a ranking function that assigns a number to each grade based on its position in the list:

orderedGrades = {"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-", "E", "W"};

rankingFunction = With[{
    posIndex = First /@ PositionIndex[orderedGrades]
    (* Use 1000 as the default value for any value that doesn't occur in orderedGrades so anything unexpected gets sorted to the end *)
    Lookup[posIndex, #, 1000] &

This ranking function can now easily be used in conjunction with SortBy:

grades = {"C", "B-", "B", "B+", "B", "C-", "E", "B", "D", 
   "D+", "C-", "B", "C", "B-", "C+", "E", "B-", "B-", "C-", "A", "C", 
   "B-", "B", "C+", "B", "C", "B+", "C+", "D-", "C", "A", "E", "B", 
   "B+", "C", "C", "D+", "D", "C", "C-", "C", "B", "D", "B", "B", "E",
    "B", "W", "W"};
SortBy[grades, rankingFunction]

{"A", "A", "B+", "B+", "B+", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B", "B-", "B-", "B-", "B-", "B-", "C+", "C+", "C+", "C", "C", "C", "C", "C", "C", "C", "C", "C", "C-", "C-", "C-", "C-", "D+", "D+", "D", "D", "D", "D-", "E", "E", "E", "E", "W", "W"}

| improve this answer | |
  • $\begingroup$ It's always nice to see different approaches to the problem. $\endgroup$ – bobthechemist Oct 26 '18 at 11:34
grades = {"C", "B-", "B", "B+", "B", "C-", "E", "B", "D", "D+", 
   "C-", "B", "C", "B-", "C+", "E", "B-", "B-", "C-", "A", "C", "B-", 
   "B",  "C+", "B", "C", "B+", "C+", "D-", "C", "A", "E", "B", "B+", "C", 
    "C", "D+", "D", "C", "C-", "C", "B", "D", "B", "B", "E", "B",  "W",  "W"};
order = {"W", "E-", "E", "E+", "D-", "D", "D+", "C-", "C", "C+", 
   "B-", "B", "B+", "A-", "A", "A+"};

BarChart[Labeled[Count[grades, #], #]& /@ order] 

enter image description here


BarChart[Count[grades, #] & /@ order, ChartLabels -> order]

same picture

| improve this answer | |

This seems like a simpler approach than other answers, i.e. no separate order list.

grades = {"C", "B-", "B", "B+", "B", "C-", "E", "B", "D", "D+", "C-", "B", "C", 
   "B-", "C+", "E", "B-", "B-", "C-", "A", "C", "B-", "B", "C+", "B", "C", "B+", 
   "C+", "D-", "C", "A", "E", "B", "B+", "C", "C", "D+", "D", "C", "C-", "C", "B", 
   "D", "B", "B", "E", "B", "W", "W"};

f1 = -PadRight[ToCharacterCode@#, 2, 44] &;

  KeySortBy[Counts @ grades, f1]
  , ChartLabels -> Automatic

enter image description here

| improve this answer | |
  • $\begingroup$ +1; however, Mean@ToCharacterCode@"+-" seems less obscure than 44. $\endgroup$ – Michael E2 Oct 26 '18 at 14:31
  • 1
    $\begingroup$ @MichaelE2 My user name entitles me to use magic values. :^) $\endgroup$ – Mr.Wizard Oct 26 '18 at 14:34

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.