5
$\begingroup$

I wondered if anyone has another or even a more direct way of finding the last dates of each month available from a list of successive dates?

I currently do the following (note: nothing special about WeatherData[] here, I just used it to generate a list of dates):

WeatherData["KMDZ", "MeanTemperature", {{2007, 1, 1}, {2007, 12, 31}, "Day"}][[All, 1]];
Last[#] & /@ SplitBy[%, DateList[#][[2]] &]

{{2007, 1, 31}, {2007, 2, 28}, {2007, 3, 31}, {2007, 4, 30}, {2007, 5, 31}, {2007, 6, 30}, {2007, 7, 31}, {2007, 8, 31}, {2007, 9,30}, {2007, 10, 31}, {2007, 11, 30}, {2007, 12, 31}}

This works fine. I just wondered if I could think about this problem in a different way.


It seems the question needs a clarification. The starting list of dates may not include every date in a given month as such; it also may not include the actual last date of any given month. I need to find the last date in any month available in the list.

$\endgroup$
4
  • 1
    $\begingroup$ You know, you could have done Last /@ (* stuff *) instead of Last[#] & /@ (* stuff *)... otherwise, your method looks perfectly straightforward. $\endgroup$ Commented Jun 25, 2012 at 14:16
  • $\begingroup$ And GatherBy[wd[[All, 1]], #[[2]] &][[All, -1]] == Last /@ SplitBy[wd[[All, 1]], DateList[#][[2]] &] $\endgroup$
    – user1066
    Commented Jun 25, 2012 at 15:39
  • $\begingroup$ @J.M. -- I like your conciseness. Thanks! $\endgroup$
    – Jagra
    Commented Jun 25, 2012 at 18:33
  • $\begingroup$ @TomD -- I hadn't considered using GatherBY[] like that. Great solution. $\endgroup$
    – Jagra
    Commented Jun 25, 2012 at 18:33

3 Answers 3

9
$\begingroup$

Since your data already has dates in the form {Y, M, D} you could do without DateList, and as mentioned by J. M. you can use Last in place of Last[#] &, therefore you could use:

Last /@ SplitBy[data, #[[2]] &]

Since as the operation above shows you are only looking at the second column, you might use a numeric operation and Pick:

Pick[data, Differences@data[[All, 2]] ~Append~ 1, 1]

This is actually quite fast (timeAvg):

Last /@ SplitBy[data, #[[2]] &] // timeAvg

Pick[data, Differences@data[[All, 2]] ~Append~ 1, 1] // timeAvg

0.00031456

0.000020992

This can be made slightly faster still by using the undocumented properties of SparseArray. (Evaluate SparseArray[{1}]["Properties"] for a list.)

data ~Extract~ 
  SparseArray[Differences@data[[All, 2]] ~Append~ 1]["NonzeroPositions"]

EDIT: The Differences methods work on the assumption that there is at least one date in each month, therefore transition points will be characterized by a delta of 1. However if that is not the cause you would need to add something like Unitize:

Pick[data, Unitize@Differences@data[[All, 2]] ~Append~ 1, 1]

For the sake of exploring less practical alternatives you could also use:

Reap[
  If[#[[2]] < #2[[2]], Sow@#] & @@@ Partition[data, 2, 1]
][[2, 1]]

Or:

Reap[
  Fold[(If[#[[2]] < #2[[2]], Sow@#]; #2) &, First@data, Rest@data]
][[2, 1]]

(Each of these misses the last date which would need to be appended.)

$\endgroup$
3
  • $\begingroup$ Interesting, concise, and elegant. Much appreciated. $\endgroup$
    – Jagra
    Commented Jun 25, 2012 at 20:37
  • $\begingroup$ @Jagra thanks; please see my EDIT for an important caveat. $\endgroup$
    – Mr.Wizard
    Commented Jun 25, 2012 at 20:55
  • $\begingroup$ With the new DateObject[], one can now also do Max[DateObject /@ #] & /@ SplitBy[datelist, #[[2]] &]. Use DateList[] if you need to convert back to lists. $\endgroup$ Commented Feb 25, 2016 at 9:07
6
$\begingroup$

Just ask for the 0th day and you'll get the last day of the previous month:

DateList[{2012, 2, 0}]

{2012, 1, 31, 0, 0, 0.}

or:

DateList@{2007, #, 0} & /@ (Range@12 + 1)

{{2007, 1, 31}, {2007, 2, 28}, {2007, 3, 31}, {2007, 4, 30}, {2007, 5, 31}, {2007, 6, 30}, {2007, 7, 31}, {2007, 8, 31}, {2007, 9, 30}, {2007, 10, 31}, {2007, 11, 30}, {2007, 12, 31}}

$\endgroup$
2
  • $\begingroup$ Somehow I had interpreted the question as asking how to determine, in a list of dates split by month, the latest date for each month. But I guess your interpretation's the one. $\endgroup$ Commented Jun 25, 2012 at 14:28
  • $\begingroup$ @J.M. -- Actually you got it right. Perhaps the question had some ambiguity. I added a clarification at the end of the original question. $\endgroup$
    – Jagra
    Commented Jun 25, 2012 at 18:45
1
$\begingroup$

Using DayRound:

SeedRandom[0];
d0 = DateList[{2020}] // DateObject
rdates = DayPlus[d0, #] & /@ RandomInteger[{1, 365}, 10]

Random Dates

DayRound[#, "EndOfMonth"] &@rdates

Rounded to the last day of the month


I use v12.2.0, so be sure to check RandomDate which is a recent addition to Mathematica.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.