# How to Create an Efficient Step Function from Data

I have list of rules from where a DateListStepPlot can be created to visualize the problem:

dateToPopulationRules = {DateObject[{2019, 1, 1}] -> 5, DateObject[{2019, 2, 1}] -> 10, DateObject[{2019, 2, 13}] -> 6, DateObject[{2019, 4, 4}] -> 1};
DateListStepPlot[List @@@ dateToPopulationRules, PlotMarkers -> Automatic, FrameLabel -> {None, "Population"}] I want to create an efficient function for population using a huge instance of dateToPopulationRules. Here's my poor implementation:

Clear[population];
population[dateObj_] := 0;
Table[
With[{dateLim = First@dpr, pop = Last@dpr},
population[dateObj_] := Condition[pop, dateObj >= dateLim]
]
, {dpr, dateToPopulationRules}];


An example showing how the population function works is below. You may want to see DownValues@population in case that helps understanding my code.

In:= population[DateObject[{2019, 3, 1}]]
Out= 5


Although this approach is effective I don't think is the most efficient. I would like to see another solution using a Nearest function or other function that can handle large data sets efficiently.

• If you want it to be efficient, it's probably best to convert the dates to AbsoluteTime. Simple numbers will beat date objects in performance most of the time. You can use Interpolation[data, InterpolationOrder -> 0] to get the interpolation behavior you're looking for. – Sjoerd Smit Nov 12 '19 at 13:39
• Wow! It looks so obvious now. Still, it would be good to see the implementation. Thanks! – Ariel Sepulveda Nov 12 '19 at 14:06

You could use Interpolation as suggested @Sjoerd, for example:

data = List @@@ dateToPopulationRules /. d_DateObject :> AbsoluteTime[d];

p = Interpolation[
data . {{-1, 0}, {0, 1}},
InterpolationOrder->0
] @* Minus;


where I need to use a double negation to get the end point behavior correct. Visualization:

Plot[
p[x],
{x, AbsoluteTime@DateObject[{2019,1,1}], AbsoluteTime@DateObject[{2019,6,1}]},
PlotRange->{0,10.5},
Ticks->{
ChartingDateTicksFunction[Automatic, DateTicksFormat -> {Automatic}],
Automatic
},
Epilog -> {PointSize[Large], Point[data]}
] If you want a faster function, you could try using StepFunction from How can the behavior of InerpolationOrder->0 be controlled:

q = StepFunction[data, Right];
Plot[
q[x],
{x, AbsoluteTime@DateObject[{2019,1,1}], AbsoluteTime@DateObject[{2019,6,1}]},
PlotRange->{0,10.5},
Ticks->{
ChartingDateTicksFunction[Automatic, DateTicksFormat -> {Automatic}],
Automatic
},
Epilog -> {PointSize[Large], Point[data]}
] Speed comparison:

times = RandomReal[
AbsoluteTime /@ {DateObject[{2019, 1, 1}], DateObject[{2019, 4, 1}]},
10^5
];

r1 = p[times]; //RepeatedTiming
r2 = q[times]; //RepeatedTiming

r1===r2


{0.14, Null}

{0.0072, Null}

True

So, using StepFunction is almost 20 times faster.

• Excellent! I assume your StepFunction will eventually be added to the Wolfram Language. – Ariel Sepulveda Nov 13 '19 at 19:55
• @ArielSepulveda I'm planning to add it as a ResourceFunction, no idea whether somebody might add a kernel function to do this. – Carl Woll Nov 13 '19 at 21:33

You may useTimeSeries with the ResamplingMethod option.

tsPop = TimeSeries[List @@@ dateToPopulationRules,
ResamplingMethod -> {"Interpolation", InterpolationOrder -> 0}] InterpolationOrder -> 0 repeats prior value until new value is defined.

tsPop[DateObject /@ {{2019, 1, 30}, {2019, 2, 1}}]

{5, 10}


Plot with DateListStepPlot.

DateListStepPlot[tsPop, Mesh -> Full] Hope this helps.

• Very useful and a new window opened into TimeSeries. Thanks! Based on a simple test using RepeatedTiming q function based on StepFunction seems to be near one order faster than tsPop. – Ariel Sepulveda Nov 13 '19 at 19:50