# elegant definition of piecewise linear functions (spline)

As far as I understand, the Spline object is the result of some approximation or interpolation, it is not for directly defining functions.

That's a pity, as that is exactly what I'd need to analyze some tax schemes. Maybe because I could not define non-overlapping intervals (all could be open on, say, the right end), my version below produces a function (?) that more-or-less restates the cases in the definition but does not evaluate for a simple real scalar argument.

What is a neater and foolproof way to accomplish this, then?

A trivial example works:

Tax [z_]:= Piecewise[{{0,z < 11265},{1,z >=11265 }}]


This does not:

Tax [z_]:= Piecewise[{
{0,z \[Element] Interval[{0 , 11265}]},
{0+ 0.0856(z-11265),z  \[Element]Interval[ {11265,13173}]   },
{1127.6088+0.0963(z-13173),z \[Element] Interval[ {13173,15009}]},
{1445.3667+0.107(z-15009),z \[Element] Interval[ {15009,16881}]},
{1806.267+0.1177(z-16881),z \[Element] Interval[ {16881,18753}]},
{   2207.2281+0.1284(z-18753),z \[Element] Interval[ {18753,20625}]},
{2648.25+0.1498(z-20625),z\[Element] Interval[{ 20625,22569}]},
{3380.8362+0.1712(z-22569),z \[Element] Interval[ {22569,24513}]},
{4196.6256+0.1926(z-24513),z \[Element] Interval[ {24513,26457}]},
{5095.61823+0.214(z-26457),z \[Element] Interval[{ 26457,28401}]},
{6077.814+0.2354(z-28401),z\[Element] Interval[ {28401,30345}]},
{   7143.213+0.2568(z-30345),z \[Element] Interval[ {30345,32289}]},
{8291.8152+0.2782(z-32289),z \[Element] Interval[ {32289,34233}]}   ,
{   9523.6206+0.2996(z-34233),z \[Element] Interval[ {34233,36177}]},
{10838.6292+0.321(z-36177),z \[Element] Interval[{ 36177,38121}]},
{12236.841+0.3424(z-38121),z\[Element] Interval[ {38121,40065}]},
{13718.256+0.3638(z-40065),z \[Element] Interval[ {40065,42009}]},
{15282.8742+0.3852(z-42009),z \[Element] Interval[{ 42009,43953}]},
{16930.6956+    0.4066(z-43953),z\[Element] Interval[ {43953,45897}]},
{18661.7202+0.4173(z-45897),z \[Element] Interval[ {45897,100002}]},
{   41730.8346+0.428(z-100002),z \[Element] Interval[ {100002,150000}]},
{64200+0.4469(z-150000),z \[Element] Interval[ {150000,200004}]},
{89381.7876+0.4578(z-200004),z > 200004 }}]


Use IntervalMemberQ[Interval[{a,b}], z] or a<=z<=b instead of Element[z,Interval[{a, b}]]:

Tax2[z_] := Tax[z] /. Element[a_, b_Interval] :> IntervalMemberQ[b, a]
Tax2[20000]


2367.34

Tax3[z_] := Tax[z] /. Element[z, Interval[{a_, b_}]] :> a <= z <= b
Tax3[20000]


2367.34

Update: Working with 3 lists as input (1) income thresholds (2) marginal tax rate in each bracket, (3) and lump-sum minimum tax in each bracket, and constructing a tax function using a combination of UnitStep and Dot:

thresholds = Append[Cases[Tax[z], Interval[{a_, b_}] :> a, All], 200004];
marginalrates = Prepend[Cases[Tax[z], a_ Plus[_, z] :> a, All], 0];
lumpsums = Join[{0, 0}, Cases[Tax[z], Plus[a_, Times[_, Plus[_, z]]] :> a, All]];

brackets = Partition[Append[thresholds, Infinity], 2, 1];
ClearAll[taX, boole]
boole[z_] := (1 - UnitStep[-#[[2]] + z]) UnitStep[-#[[1]] + z] & /@ brackets;
taX[z_] := (lumpsums + marginalrates (z - thresholds)).boole[z]

And @@ (taX[#] == Tax2[#] & /@ RandomInteger[{0, 250000}, 1000])


True

Note: The tax function in OP is not continuous; there are upward jumps at threshold points. Since the three input lists are extracted from OP's Tax function, taX also exhibits the same jumps.

Update 2: To get a continuous tax function we can work with the lists thresholds and marginalrates as follows:

ClearAll[cTax]
cTax[z_?NumericQ] := NIntegrate[marginalrates.boole[t], {t, 0, z}]
Plot[{taX[z], cTax[z]}, {z, 0, 70000}, PlotLegends -> "Expressions"]


• Nice. By the way, it might be even better to do away with the repetition of the knots and the constant terms that are supposed to ensure continuity at those kinks. If I (or a tax accountant) made any typo in the manual calculations, we might not even end up with a continuous piecewise linear function (spline). Commented Jul 1, 2019 at 15:26
• @László To wit, shouldn't the 1127.6088 in the third formula be 163.325? Commented Jul 1, 2019 at 18:16
• @László, please see the update.
– kglr
Commented Jul 2, 2019 at 22:27
• This is very nice, @kglr, if only I could upvote twice. Sorry about the dicontinuous jumps, though, the lump-sum minimums are not as quirky as the look in OP, which must be buggy. Think of the thresholds/knots where higher MTRs should kick in, otherwise the schedule is supposed to be continuous. Does that allow for a simpler construct? Commented Jul 3, 2019 at 9:30