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$
  • 1
    $\begingroup$ You know, you could have done Last /@ (* stuff *) instead of Last[#] & /@ (* stuff *)... otherwise, your method looks perfectly straightforward. $\endgroup$ – J. M. will be back soon Jun 25 '12 at 14:16
  • $\begingroup$ And GatherBy[wd[[All, 1]], #[[2]] &][[All, -1]] == Last /@ SplitBy[wd[[All, 1]], DateList[#][[2]] &] $\endgroup$ – user1066 Jun 25 '12 at 15:39
  • $\begingroup$ @J.M. -- I like your conciseness. Thanks! $\endgroup$ – Jagra Jun 25 '12 at 18:33
  • $\begingroup$ @TomD -- I hadn't considered using GatherBY[] like that. Great solution. $\endgroup$ – Jagra Jun 25 '12 at 18:33
8
$\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$
  • $\begingroup$ Interesting, concise, and elegant. Much appreciated. $\endgroup$ – Jagra Jun 25 '12 at 20:37
  • $\begingroup$ @Jagra thanks; please see my EDIT for an important caveat. $\endgroup$ – Mr.Wizard Jun 25 '12 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$ – J. M. will be back soon Feb 25 '16 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$
  • $\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$ – J. M. will be back soon Jun 25 '12 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 Jun 25 '12 at 18:45

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.