TransWikia.com

Better way to sort course grades

Mathematica Asked on January 14, 2021

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" -> "-"}];
Reverse@Sort@
  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?

6 Answers

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", 
  "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

Correct answer by Pillsy on January 14, 2021

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"};

Then,

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

BarChart[nl, ChartLabels -> gl]

grade chart

Answered by J. M.'s ennui on January 14, 2021

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,

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

Mathematica graphics

An alternative that is often useful:

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

Mathematica graphics

Answered by Szabolcs on January 14, 2021

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

Alternatively,

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

same picture

Answered by kglr on January 14, 2021

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"}

Answered by Sjoerd Smit on January 14, 2021

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] &;

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

enter image description here

Answered by Mr.Wizard on January 14, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP