4
$\begingroup$

Given two lists, how can I compute the area between their plots? (the blue region in the image below).

list plot

The horizontal distance between points in the list can be assumed to be 1, as done by ListPlot automatically in the example image.

For my use case, performance is really important so I would prefer a method that does not require doing any plotting.

Edit, solution chosen:

I finally went with this compilated version of the accepted version, which is very fast, at least on my tests:

BlocksAreaC = Compile[{{x, _Real, 2}},
   Block[
    {
     diff = x[[2]] - x[[1]],
     c = (-x[[1, 1]] + x[[2, 1]])
     },
    If[
     Times @@ Sign[diff] == 1,
     Total[Abs[diff]]/2,
     If[c == 0, 0,
      With[
       {h = c/(-x[[1, 1]] + x[[1, 2]] + x[[2, 1]] - x[[2, 2]])},
       Total[Abs[diff]*{h, 1 - h}/2]
       ]
      ]
     ]
    ],
   Parallelization -> True, RuntimeAttributes -> {Listable}, 
   CompilationTarget -> "C", RuntimeOptions -> "Speed", 
   CompilationOptions -> {"ExpressionOptimization" -> True, 
     "InlineCompiledFunctions" -> True, 
     "InlineExternalDefinitions" -> True}
   ];
areaBetweenLists7C = Compile[{{l1, _Real, 1}, {l2, _Real, 1}},
   Module[
    {
     s1 = Partition[l1, 2, 1],
     s2 = Partition[l2, 2, 1]
     },
    Total[Map[BlocksAreaC, Transpose[{s1, s2}]]]
    ],
   Parallelization -> True, RuntimeAttributes -> {Listable}, 
   CompilationTarget -> "C", RuntimeOptions -> "Speed", 
   CompilationOptions -> {"ExpressionOptimization" -> True, 
     "InlineCompiledFunctions" -> True, 
     "InlineExternalDefinitions" -> True}
   ];
$\endgroup$
4
  • 1
    $\begingroup$ As you mentioned in a comment to the answer, performance is very important for you in this question. Please consider adding that to the original question! Obviously, if you need performance, the answers will end up being very different. In addition, if performance is what you require, but the answer doesn't give the performance you require, it's worth not accepting the answer. Questions with accepted answers tend not to get very many new answers. $\endgroup$
    – march
    Commented Feb 4, 2020 at 0:28
  • $\begingroup$ @march Thanks, done. $\endgroup$
    – José D.
    Commented Feb 4, 2020 at 14:48
  • $\begingroup$ Do you have some larger test data sets? It would be interesting to play with the timings. $\endgroup$
    – MikeY
    Commented Feb 5, 2020 at 4:47
  • $\begingroup$ @MikeY you can use RandomInteger[{0, 10}, {2, numberOfSamples}] and change the number of samples to whatever you like :) $\endgroup$
    – Fraccalo
    Commented Feb 5, 2020 at 8:37

4 Answers 4

2
$\begingroup$

One could also go with a fully analytical approach:

l1 = {1, 2, 4, 3, 1};
l2 = {3, 6, 5, 1, 0};

s1 = Subsequences[l1, {2}];
s2 = Subsequences[l2, {2}];
s = Transpose[{s1, s2}];

Edit: to avoid the code breaking when a polygon has area = 0, one can replace s with:

s = Select[Transpose[{s1, s2}], #[[1]] != #[[2]] &]

To speed up computation, one might want to add this check in a nested if within the BlocksArea function, but it would need some testing for checking which solution is the fastest.

BlocksArea[x_] := Block[{diff = x[[2]] - x[[1]], h},
  If[Times @@ Sign[diff] == 1,
   Total[Abs[diff]]/2,
   h = (-x[[1, 1]] + x[[2, 1]])/(-x[[1, 1]] + x[[1, 2]] + x[[2, 1]] - x[[2, 2]]);
   Total[Abs[diff]*{h, 1 - h}/2]
   ]
  ]

Total[BlocksArea /@ s]

47/6

what the code does is computing the area of the regions between 3 point piece by piece: if the points form trapezoid shape the area is computed as the sum of the bases, times the height, divided by 2. if the points form two triangles, one can find the height of the triangles and then the corresponding areas.

It looks like the code in my answer runs faster than the ones above (see a non-exhaustive benchmark below), and I'm pretty sure that one can speed up my code by rewriting it (keeping the same underline concept) for efficiently compiling it to C.

I think it's also suitable for parallelisation, as the lists of points can be split in the number of cores/kernel available for computing the area of each piece on a different thread.

Here a speed comparison:

kglr, code in point 3:

RepeatedTiming[
 {if1, if2} = 
  Interpolation[#, InterpolationOrder -> 1] & /@ 
   lists;
 NIntegrate[Abs[if1[t] - if2[t]], {t, 1, 5}]
 ]

{0.011, 7.83333}

J. M. is in limbo♦ code:

RepeatedTiming[
 Integrate[Abs[Apply[Subtract, makePW[#, t] & /@ lists]], {t, 1, 5}]
 ]

{0.020, 47/6}

my code:

RepeatedTiming[s1 = Subsequences[l1, {2}];
 s2 = Subsequences[l2, {2}];
 s = Transpose[{s1, s2}];
 Total[BlocksArea /@ s]]

{0.00033, 47/6}

EDIT: Difference when comparing two long lists (10000 samples)

generating the lists:

lists = {l1, l2} = RandomInteger[{0, 10}, {2, 10000}];

output of the timing (in the same order as above):

kglr: 0.66

J. M. is in limbo♦: 4.4

Fraccalo: 0.19

$\endgroup$
4
  • $\begingroup$ Sorry but this fails for l1={1,3}; l2={2,4} $\endgroup$
    – José D.
    Commented Feb 4, 2020 at 22:08
  • $\begingroup$ @JoséD. it works on my machine: it gives me a 1/0 error message but still returns the correct answer :) (which is 1) $\endgroup$
    – Fraccalo
    Commented Feb 5, 2020 at 8:26
  • $\begingroup$ @JoséD. The error arises because the function it's trying to find the intercept of two parallel lines. However, it doesn't need that value for computing the area! There are 2 improvements you can do: put the h value in the else part of the If command, and also add an exception handler for when the area of the polygon is 0 because the two lines are overlapped. I think this last case would also rise an error, but it's fairly easy to deal with it :) $\endgroup$
    – Fraccalo
    Commented Feb 5, 2020 at 8:35
  • $\begingroup$ @JoséD. I added the edits in the new version of my answer. I changed s as follows: s = Select[Transpose[{s1, s2}], #[[1]] != #[[2]] &] and moved the h definition in the function $\endgroup$
    – Fraccalo
    Commented Feb 5, 2020 at 8:46
9
$\begingroup$
  1. You can extract the polygons in llp using Cases:

llp = ListLinePlot[{{1, 2, 4, 3, 1}, {3, 6, 5, 1, 0}}, Filling -> {1 -> {2}}];

polygons = Cases[Normal@llp, _Polygon, All]

{Polygon[{{1.,1.},{2.,2.},{3.,4.},{3.33333,3.66667},{3.33333,3.66667},{3.,5.},{2.,6.},{1.,3.}}],
Polygon[{{3.33333,3.66667},{4.,1.},{5.,0.},{5.,1.},{4.,3.},{3.33333,3.66667}}]}

Area /@ polygons

{5.66667, 2.16667}

Total @ %

7.83334

  1. You can use BoundaryDiscretizeGraphics to get a MeshRegion and get its Area or RegionMeasure:

Area @ BoundaryDiscretizeGraphics @ llp

7.833333333333334

RegionMeasure @ BoundaryDiscretizeGraphics[llp]

7.833333333333334

  1. Use Interpolation on the two lists to get two functions and NIntegrate to get area between the two:

lists = {{1, 2, 4, 3, 1}, {3, 6, 5, 1, 0}};
{if1, if2} = Interpolation[#, InterpolationOrder -> 1] & /@ lists; (*thanks: Mr.Wizard*)

NIntegrate[Abs[if1[t] - if2[t]], {t, 1, 5}]

7.833333171170218

$\endgroup$
7
  • $\begingroup$ Is there a way to achieve this without plotting? I guess that will be slow, and I need to compute areas on a lot of large lists. Thanks for your answer. $\endgroup$
    – José D.
    Commented Feb 3, 2020 at 23:53
  • $\begingroup$ @JoséD. you can use Interpolation to get two functions and use NIntegrate . $\endgroup$
    – kglr
    Commented Feb 3, 2020 at 23:59
  • $\begingroup$ Performance is really important for my usecase, and I feel like there has to be more efficient alternatives (the shapes are just triangles). Anyway thank you so much for your answer, I am accepting it for now. $\endgroup$
    – José D.
    Commented Feb 4, 2020 at 0:17
  • 1
    $\begingroup$ A slightly simpler form works: {if1, if2} = Interpolation[#, InterpolationOrder -> 1] & /@ lists $\endgroup$
    – Mr.Wizard
    Commented Feb 4, 2020 at 4:50
  • 1
    $\begingroup$ @Fraccalo, I did try that; did not help much. However, usingMethod -> {Automatic, "SymbolicProcessing" ->False} did improve timing considerably. $\endgroup$
    – kglr
    Commented Feb 5, 2020 at 9:58
1
$\begingroup$

In fact, you can compute the exact answer for this case if you explicitly assemble the piecewise linear function representing the two "connect-the-dots" plots in the OP, and then feed the integrand to Integrate[]. Here's one way to derive the required piecewise linear interpolant:

makePW[ya_?VectorQ, t_] := 
    Piecewise[MapIndexed[{InterpolatingPolynomial[{{#2, #1[[1]]}, {#2 + 1, #1[[2]]}}, t],
                          First[#2] <= t <= First[#2] + 1} &, Partition[ya, 2, 1]]]

Then,

lists = {{1, 2, 4, 3, 1}, {3, 6, 5, 1, 0}};

Integrate[Abs[Apply[Subtract, makePW[#, t] & /@ lists]], {t, 1, 5}]
   47/6

One can also reformulate an equivalent procedure in terms of UnitStep[]:

makePW2[ya_?VectorQ, t_] := 
    With[{lp = Most[ya] + Differences[ya] (t - Range[Length[ya] - 1])}, 
         First[lp] + Differences[lp].UnitStep[t - Range[2, Length[lp]]]]

and Integrate[Abs[Apply[Subtract, makePW2[#, t] & /@ lists]], {t, 1, 5}] should yield the same answer.

$\endgroup$
0
$\begingroup$

EDIT

This approach doesn't work, as the method of choosing points to include in the polygon (counting crossings of rays extending from point) results in points sometimes in, sometimes out. With enough points, you are guaranteed for it to fail.

Keeping this answer as it has useful info, and to head off any future ventures down this path.


This is easy to state

upper = Transpose@{Range[5], {1, 2, 4, 3, 1}}
lower = Transpose@{Range[5], {3, 6, 5, 1, 0}}

polyPts = Join@@{upper,Reverse@lower};
ptOrder = {1,2,3,4,5,6,7,8,9,10,1};

poly = Polygon[polyPts, ptOrder];
Graphics[poly]

enter image description here

Integrate[1, {x, y} ∈ poly]
(* 7.83333 *)

Note: works in Mma 12 but not in 11, it appears.

$\endgroup$
2
  • $\begingroup$ Using mathematica 11.2, I get: "Integrate::ilim: Invalid integration variable or limit(s) in {x,y}[Element]Polygon[{{1,1},{2,2},{3,4},{4,3},{5,1},{5,0},{4,1},{3,5},{2,6},{1,3}},{1,2,3,4,5,6,7,8,9,10,1}]." $\endgroup$
    – José D.
    Commented Feb 4, 2020 at 22:33
  • $\begingroup$ My bad then. If you edit your quesiton I'll remove the down vote (it won't let me otherwise) $\endgroup$
    – José D.
    Commented Feb 5, 2020 at 0:17

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.