Find the area between two curves plotted in matplotlib (fill_between area)

后端 未结 5 1170
生来不讨喜
生来不讨喜 2020-12-08 08:39

I have a list of x and y values for two curves, both having weird shapes, and I don\'t have a function for any of them. I need to do two things: (1) plot it and shade the ar

相关标签:
5条回答
  • 2020-12-08 08:42

    The area_between_two_curves function in pypi library similaritymeasures (released in 2018) might give you what you need. I tried a trivial example on my side, comparing the area between a function and a constant value and got pretty close tie-back to Excel (within 2%). Not sure why it doesn't give me 100% tie-back, maybe I am doing something wrong. Worth considering though.

    0 讨论(0)
  • 2020-12-08 08:45

    Define your two curves as functions f and g that are linear by segment, e.g. between x1 and x2, f(x) = f(x1) + ((x-x1)/(x2-x1))*(f(x2)-f(x1)). Define h(x)=abs(g(x)-f(x)). Then use scipy.integrate.quad to integrate h.

    That way you don't need to bother about the intersections. It will do the "trapeze summing" suggested by ch41rmn automatically.

    0 讨论(0)
  • 2020-12-08 08:47

    Your set of data is quite "nice" in the sense that the two sets of data share the same set of x-coordinates. You can therefore calculate the area using a series of trapezoids.

    e.g. define the two functions as f(x) and g(x), then, between any two consecutive points in x, you have four points of data:

    (x1, f(x1))-->(x2, f(x2))
    (x1, g(x1))-->(x2, g(x2))
    

    Then, the area of the trapezoid is

    A(x1-->x2) = ( f(x1)-g(x1) + f(x2)-g(x2) ) * (x2-x1)/2                         (1)
    

    A complication arises that equation (1) only works for simply-connected regions, i.e. there must not be a cross-over within this region:

    |\             |\/|
    |_|     vs     |/\|
    

    The area of the two sides of the intersection must be evaluated separately. You will need to go through your data to find all points of intersections, then insert their coordinates into your list of coordinates. The correct order of x must be maintained. Then, you can loop through your list of simply connected regions and obtain a sum of the area of trapezoids.

    EDIT:

    For curiosity's sake, if the x-coordinates for the two lists are different, you can instead construct triangles. e.g.

    .____.
    |   / \
    |  /   \
    | /     \
    |/       \
    ._________.
    

    Overlap between triangles must be avoided, so you will again need to find points of intersections and insert them into your ordered list. The lengths of each side of the triangle can be calculated using Pythagoras' formula, and the area of the triangles can be calculated using Heron's formula.

    0 讨论(0)
  • 2020-12-08 09:00

    The area calculation is straightforward in blocks where the two curves don't intersect: thats the trapezium as has been pointed out above. If they intersect, then you create two triangles between x[i] and x[i+1], and you should add the area of the two. If you want to do it directly, you should handle the two cases separately. Here's a basic working example to solve your problem. First, I will start with some fake data:

    #!/usr/bin/python
    import numpy as np
    
    # let us generate fake test data
    x = np.arange(10)
    y1 = np.random.rand(10) * 20
    y2 = np.random.rand(10) * 20
    

    Now, the main code. Based on your plot, looks like you have y1 and y2 defined at the same X points. Then we define,

    z = y1-y2
    dx = x[1:] - x[:-1]
    cross_test = np.sign(z[:-1] * z[1:])
    

    cross_test will be negative whenever the two graphs cross. At these points, we want to calculate the x coordinate of the crossover. For simplicity, I will calculate x coordinates of the intersection of all segments of y. For places where the two curves don't intersect, they will be useless values, and we won't use them anywhere. This just keeps the code easier to understand.

    Suppose you have z1 and z2 at x1 and x2, then we are solving for x0 such that z = 0:

    # (z2 - z1)/(x2 - x1) = (z0 - z1) / (x0 - x1) = -z1/(x0 - x1)
    # x0 = x1 - (x2 - x1) / (z2 - z1) * z1
    x_intersect = x[:-1] - dx / (z[1:] - z[:-1]) * z[:-1]
    dx_intersect = - dx / (z[1:] - z[:-1]) * z[:-1]
    

    Where the curves don't intersect, area is simply given by:

    areas_pos = abs(z[:-1] + z[1:]) * 0.5 * dx # signs of both z are same
    

    Where they intersect, we add areas of both triangles:

    areas_neg = 0.5 * dx_intersect * abs(z[:-1]) + 0.5 * (dx - dx_intersect) * abs(z[1:])
    

    Now, the area in each block x[i] to x[i+1] is to be selected, for which I use np.where:

    areas = np.where(cross_test < 0, areas_neg, areas_pos)
    total_area = np.sum(areas)
    

    That is your desired answer. As has been pointed out above, this will get more complicated if the both the y graphs were defined at different x points. If you want to test this, you can simply plot it (in my test case, y range will be -20 to 20)

    negatives = np.where(cross_test < 0)
    positives = np.where(cross_test >= 0)
    plot(x, y1)
    plot(x, y2)
    plot(x, z)
    plt.vlines(x_intersect[negatives], -20, 20)
    
    0 讨论(0)
  • 2020-12-08 09:05

    I had the same problem.The answer below is based on an attempt by the question author. However, shapely will not directly give the area of the polygon in purple. You need to edit the code to break it up into its component polygons and then get the area of each. After-which you simply add them up.

    Area Between two lines

    Consider the lines below:

    Sample Two lines If you run the code below you will get zero for area because it takes the clockwise and subtracts the anti clockwise area:

    from shapely.geometry import Polygon
    
    x_y_curve1 = [(1,1),(2,1),(3,3),(4,3)] #these are your points for curve 1 
    x_y_curve2 = [(1,3),(2,3),(3,1),(4,1)] #these are your points for curve 2 
    
    polygon_points = [] #creates a empty list where we will append the points to create the polygon
    
    for xyvalue in x_y_curve1:
        polygon_points.append([xyvalue[0],xyvalue[1]]) #append all xy points for curve 1
    
    for xyvalue in x_y_curve2[::-1]:
        polygon_points.append([xyvalue[0],xyvalue[1]]) #append all xy points for curve 2 in the reverse order (from last point to first point)
    
    for xyvalue in x_y_curve1[0:1]:
        polygon_points.append([xyvalue[0],xyvalue[1]]) #append the first point in curve 1 again, to it "closes" the polygon
    
    polygon = Polygon(polygon_points)
    area = polygon.area
    print(area)
    

    The solution is therefore to split the polygon into smaller pieces based on where the lines intersect. Then use a for loop to add these up:

    from shapely.geometry import Polygon
    
    x_y_curve1 = [(1,1),(2,1),(3,3),(4,3)] #these are your points for curve 1 
    x_y_curve2 = [(1,3),(2,3),(3,1),(4,1)] #these are your points for curve 2 
    
    polygon_points = [] #creates a empty list where we will append the points to create the polygon
    
    for xyvalue in x_y_curve1:
        polygon_points.append([xyvalue[0],xyvalue[1]]) #append all xy points for curve 1
    
    for xyvalue in x_y_curve2[::-1]:
        polygon_points.append([xyvalue[0],xyvalue[1]]) #append all xy points for curve 2 in the reverse order (from last point to first point)
    
    for xyvalue in x_y_curve1[0:1]:
        polygon_points.append([xyvalue[0],xyvalue[1]]) #append the first point in curve 1 again, to it "closes" the polygon
    
    polygon = Polygon(polygon_points)
    area = polygon.area
    
    x,y = polygon.exterior.xy
        # original data
    ls = LineString(np.c_[x, y])
        # closed, non-simple
    lr = LineString(ls.coords[:] + ls.coords[0:1])
    lr.is_simple  # False
    mls = unary_union(lr)
    mls.geom_type  # MultiLineString'
    
    Area_cal =[]
    
    for polygon in polygonize(mls):
        Area_cal.append(polygon.area)
        Area_poly = (np.asarray(Area_cal).sum())
    print(Area_poly)
    
    0 讨论(0)
提交回复
热议问题