6
$\begingroup$

I have an association where the keys are the days of the week:

  assoc = <|Wednesday -> 122150, Thursday -> 119450, Friday -> 124350, 
  Saturday -> 92000, Sunday -> 85800, Monday -> 107900, 
  Tuesday -> 116500|>

I would like to sort it by day of week, with the usual ordering (Sunday, Monday, Tuesday....)

How would I do this using KeySort or KeySortBy? I've tried these, and none of them work:

KeySortBy[assoc, DayName]
KeySortBy[assoc, DateValue]
KeySortBy[assoc, Greater]

Perhaps the fact that Greater works for dates (see below) but not for day names means that MMA doesn't recognize Sunday, Monday, Tuesday... as canonical ordering for day names.

Thus far, my only solution is to manually reconstruct the association with the proper ordering:

Association[{Sunday -> assoc[Sunday], Monday -> assoc[Monday]}, 
 Tuesday -> assoc[Tuesday], Wednesday -> assoc[Wednesday], 
 Thursday -> assoc[Thursday], Friday -> assoc[Friday], 
 Saturday -> assoc[Saturday]] 

<|Sunday -> 85800, Monday -> 107900, Tuesday -> 116500, Wednesday -> 122150, Thursday -> 119450, Friday -> 124350, Saturday -> 92000|>

Note that KeySortBy can be used where the keys are dates:

assoc2 = <|{2020, 1, 2} -> 3200, {2020, 1, 3} -> 3700, {2020, 1, 5} ->
    2400, {2020, 1, 6} -> 3550, {2020, 1, 7} -> 4550, {2020, 1, 4} -> 
   2250, {2020, 1, 1} -> 2200, {2020, 1, 8} -> 3650, {2020, 1, 10} -> 
   4700, {2020, 1, 9} -> 4050|>

assoc3 = <|DateObject@{2020, 1, 2} -> 3200, 
  DateObject@{2020, 1, 3} -> 3700, DateObject@{2020, 1, 5} -> 2400, 
  DateObject@{2020, 1, 6} -> 3550, DateObject@{2020, 1, 7} -> 4550, 
  DateObject@{2020, 1, 4} -> 2250, DateObject@{2020, 1, 1} -> 2200, 
  DateObject@{2020, 1, 8} -> 3650, DateObject@{2020, 1, 10} -> 4700, 
  DateObject@{2020, 1, 9} -> 4050|>

Both of these give the proper ordering:

KeySortBy[assoc2, Greater]
KeySortBy[assoc3, Greater]

enter image description here

UPDATE

Given that the days of the week (Sunday, Monday, Tuesday...) are system-defined entities, I was curious whether MMA would have a built-in rule that could be used, in combination with KeySortBy, to order them in the usual way (or one of the usual ways). [Something whose form looked like KeySortBy[assoc, Greater] or KeySortBy[assoc, ISOWeekDay], neither of which work.]

Based on the answers kindly provided by kglr, creidhne, and Daniel Huber, I now understand that's not the case. Their answers all rely on first creating a rule explicitly showing the desired ordering (e.g., <|Sunday -> 1, Monday -> 2, Tuesday -> 3,....), and then sorting based on that rule using KeySortBy.

Using KeySortBy, kglr came up with the simplest syntax:

KeySortBy[PositionIndex[System`DateObjectDump`$DaysOfTheWeek]] @ assoc

so I've accepted his as the answer.

Having said that, given that one always needs to feed MMA the explict ordering anyways, it seems the simplest syntax of all would be obtained using KeyTake instead of KeySortBy:

KeyTake[assoc, System`DateObjectDump`$DaysOfTheWeek]

<|Sunday -> 85800, Monday -> 107900, Tuesday -> 116500, Wednesday -> 122150, Thursday -> 119450, Friday -> 124350, Saturday -> 92000|>

$\endgroup$
6
$\begingroup$
dayNameToISOWeekDay = DateValue[DayRound[{2021, 1}, #], "ISOWeekDay"] &;

KeySortBy[dayNameToISOWeekDay] @ assoc
 <|Monday -> 107900, Tuesday -> 116500, Wednesday -> 122150, 
   Thursday -> 119450, Friday -> 124350, Saturday -> 92000, 
   Sunday -> 85800|>

Update: To have the week start from Sunday:

isoWeekDay = DateValue[{ToString @ #, {"DayName"}}, "ISOWeekDay"] &;

KeySortBy[isoWeekDay] @ assoc
 <|Sunday -> 85800, Monday -> 107900, Tuesday -> 116500, 
  Wednesday -> 122150, Thursday -> 119450, Friday -> 124350, 
  Saturday -> 92000|>

Alternatively,

KeySortBy[PositionIndex[System`DateObjectDump`$DaysOfTheWeek]] @ assoc
 <|Sunday -> 85800, Monday -> 107900, Tuesday -> 116500, 
  Wednesday -> 122150, Thursday -> 119450, Friday -> 124350, 
  Saturday -> 92000|>
$\endgroup$
3
  • 1
    $\begingroup$ Interesting approach! You used DayRound to convert the DOW (day of week) to a DateObject of arbitrary date that contained that DOW, and then used DateValue + "ISOWeekDay" to convert the DOW to an integer. Alas, MMA's implementation of "ISOWeekDay" starts with Monday instead of Sunday! This is the same as Python's (pythontic.com/datetime/date/isoweekday), but is contrary to MMA's own documentation, which has Wed = 4 (reference.wolfram.com/language/ref/DateString.html). To fix this, I did: dayNameToISOWeekDay = Mod[DateValue[DayRound[{2021, 1}, #], "ISOWeekDay"], 7] &; $\endgroup$ – theorist May 8 at 2:49
  • $\begingroup$ @theorist, please see the update. $\endgroup$ – kglr May 8 at 3:59
  • 1
    $\begingroup$ Given that one always needs to feed MMA the explict ordering anyways, it seems the simplest syntax of all would be obtained using KeyTake instead of KeySortBy: KeyTake[assoc, SystemDateObjectDump$DaysOfTheWeek] [See update at end of my question] $\endgroup$ – theorist May 8 at 23:37
6
$\begingroup$

You could define a function, to be used by "SortBy", that determines the ordering. E.g.:

assoc = <|Wednesday -> 122150, Thursday -> 119450, Friday -> 124350, 
  Saturday -> 92000, Sunday -> 85800, Monday -> 107900, 
  Tuesday -> 116500|>

by[day_] = 
  Switch[day, Sunday, 1, Monday, 2, Tuesday, 3, Wednesday, 4, 
   Thursday, 5, Friday, 6, Saturday, 7];

KeySortBy[assoc, by]

enter image description here

$\endgroup$
1
  • $\begingroup$ Thanks, that works. But I was hoping to directly leverage the fact that Mathematica recognizes DayNames to create something simpler, like what I'm able to do for dates (e.g., KeySortBy[assoc2, Greater]) $\endgroup$ – theorist May 7 at 19:31
2
$\begingroup$

The canonical sort of the values returned by DayName is alphabetic, so the Greater parameter sorts as Friday-to-Wednesday in alphabetical order.

We can use an association to map the day names to the Sunday-to-Saturday sort order. The association avoids the need to use a function with sort functions. Here are two ways to create the association.

daynameOrder = AssociationThread[DayName/@Tuples[{{0}, {0}, Range[5, 11]}], Range[7]];
daynameOrder = AssociationThread[Symbol/@CalendarData["Gregorian", "DayNames"], Range[7]];

To sort an association with KeySortBy, use:

assoc = <|Wednesday -> 122150, Thursday -> 119450, Friday -> 124350, 
  Saturday -> 92000, Sunday -> 85800, Monday -> 107900, 
  Tuesday -> 116500|>;

KeySortBy[daynameOrder] @ assoc

or

KeySortBy[assoc, daynameOrder]

<| Sunday->85800, Monday->107900, Tuesday->116500, Wednesday->122150, Thursday->119450, Friday->124350, Saturday->92000 |>

$\endgroup$
2
$\begingroup$

An alternative to using SortBy[..., f] is to create a dedicated ordering function, which then can be used in any sorting operation. For example, let us write a WeekOrder function, from Monday to Sunday:

weekdays = {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday};
Do[WeekOrder[weekdays[[i]], weekdays[[j]]] = Order[i, j], {i, 7}, {j, 7}]

Then we can do

assoc = <|Wednesday -> 122150, Thursday -> 119450, Friday -> 124350, Saturday -> 92000, Sunday -> 85800, Monday -> 107900, Tuesday -> 116500|>;

In[]:= KeySort[assoc, WeekOrder]
Out[]= <|Monday -> 107900, Tuesday -> 116500, Wednesday -> 122150, Thursday -> 119450, Friday -> 124350, Saturday -> 92000, Sunday -> 85800|>

WeekOrder can also be used with Ordering:

In[]:= Ordering[Keys[assoc], 2, WeekOrder]
Out[]= {6, 7}

or any other sorting function:

In[]:= OrderedQ[{Monday, Sunday}, WeekOrder]
Out[]= True

For example, this will place all Mondays first, then all Tuesdays, etc:

SortBy[DateRange[Today, Today + Quantity[1, "Month"]], DayName, WeekOrder]
$\endgroup$

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.