My approach includes several changes:
Firstly, w.r.t. the ticks in all likelihood you will be using the same or almost the same tick labels for many plots. So therefore it seems more efficient to construct a tick list or maybe 2 or 3 of them and just keep calling that list, rather than keep doing very slow date calculations over and over. Here is an example with major ticks every year, minor ticks every quarter, and the major tick label (the year) centred on July 1. Your last plot had quarter ticks so I thought I would add them.
There may be more direct ways to do this but I have made it line by line to hopefully make it more transparent.
numberOfYears = 20;
years = DatePlus[{1998, 1, 1}, {#, "Year"}] & /@ Range[20];
years = AbsoluteTime /@ years;
major = Transpose[{years, Table["", {20}],
Table[{0.01, 0.`}, {20}]}];
quarters = DatePlus[{1998, 1, 1}, {#, "Quarter"}] & /@ Range[80];
midYear = Cases[quarters, {_, 7, 1}];
quarters = DeleteCases[quarters, {_, 7, 1}];
quarters = AbsoluteTime /@ quarters;
yearLabels = ToString /@ midYear[[All, 1]];
midYear = AbsoluteTime /@ midYear;
minor = Join[
Transpose[{quarters, Table["", {Length[quarters]}],
Table[{0.005, 0.`}, {Length[quarters]}]}],
Transpose[{midYear, yearLabels,
Table[{0.005, 0.`}, {Length[midYear]}]}]];
minor = SortBy[minor, First];
ticks = Join[major, minor]
Get some data:
msft = FinancialData["MSFT", "Jan. 1, 2000"]
ibm = FinancialData["IBM", "Jan. 1, 2000"]
(* I've excluded a description of the plot options *)
Timing[DateListPlot[{msft, ibm}, Joined -> True, opts]]
0.959088
It is much faster to work with AbsoluteTime
for date and time series. The plot below is rendered in 0.7sec including all the conversions to absolute time shown next:
Timing[
msft1 = msft;
ibm1 = ibm;
msft1[[All, 1]] = AbsoluteTime /@ msft[[All, 1]];
ibm1[[All, 1]] = AbsoluteTime /@ ibm[[All, 1]];
DateListPlot[{msft1, ibm1}, Joined -> True, opts]]
0.712620
But even more generally, it seems that DateListPlot
is just an inefficient function period. We can make an identical plot using ListLinePlot
(or ListPlot
) much faster:
Timing[ListLinePlot[{msft1, ibm1}, FrameTicks -> {{Automatic, Automatic},
{ticks, Automatic}}, opts]
0.120186
(for accurate comparison add ~0.2 sec for the absolute time conversions)
Also it is possible to do these tick creations using a function:
ticks[data_] := Module[{years, quarters},
years = Switch[Length[Dimensions[data]],
2, {data[[1, 1, 1]], data[[-1, 1, 1]]},
3, {Min[data[[All, 1, 1, 1]]], Max[data[[All, -1, 1, 1]]]}
];
quarters =
Flatten[{{#, 1, 1}, {#, 4, 1}, {#, 7, 1}, {#, 10, 1}} & /@ (Range @@
years), 1];
quarters = AbsoluteTime /@ quarters;
Transpose[{quarters,
Flatten[Table[{"", "", i, ""}, {i, Range @@ years}], 1],
Flatten[Table[{{0.01, 0.}, {0.005, 0.}, {0.005, 0.}, {0.005,
0.}}, {i, Range @@ years}], 1]}]
]
but the point of the current approach is that there is no need/point in recreating the same. or similar, tick lists over and over and over whenever you plot data. Using ListLinePlot
with a tick library seems most efficient for regular production plotting of data (with certain date range characteristics).
Edit
Ticks for the bottom plot. Same principle. I have used a line break to make the year labels appear a line below the quarter labels. This is not an exact solution because your requirement is to have the year display at the end of the plot regardless. This can be added in manually or programmatically but for now this is what I have because it is after 10am and I need to be doing other things :)
quarters = DatePlus[{1998, 1, 1}, {#, "Quarter"}] & /@ Range[80];
quarters = AbsoluteTime /@ quarters;
midQuarter = DatePlus[{1998, 2, 15}, {#, "Quarter"}] & /@ Range[80];
midQuarter = AbsoluteTime /@ midQuarter;
yearLabels = Table["\n" <> ToString[i], {i, 1998, 2017}];
yearLabels = Flatten[{"", "", #, ""} & /@ yearLabels];
quarterLabels = Flatten@Table[{"M", "J", "S", "D"}, {20}];
ticks2 = Join[
Transpose[{midQuarter, quarterLabels,
Table[{0., 0.}, {Length[midQuarter]}]}],
Transpose[{quarters, yearLabels,
Table[{0.005, 0.`}, {Length[quarters]}]}]];
ticks2 = SortBy[ticks2, First];
ListLinePlot[msft1[[-400 ;; -1]],
FrameTicks -> {{Automatic, Automatic}, {ticks2, Automatic}},
ImagePadding -> {{50, 10}, {40, 5}}, opts]
In summary my approach to this, given that I already know a little bit about what you want to do, is to create a "library" of tick lists that you can call for each particular data set with minimal dynamic modification to the list if an when required; and to use ListLinePlot
instead of DateListPlot
.