Matplotlib streamplot arrows pointing the wrong way

前端 未结 2 1877
你的背包
你的背包 2021-02-06 13:51

I am generating a groundwater elevation contour and a streamplot in matplotlib

The contour indicates that the elevation is decreasing in many areas but the groundwater

2条回答
  •  后悔当初
    2021-02-06 14:26

    Summary

    I'm guessing, but your problem is probably because you have an inherent transpose going on. 2D numpy arrays are indexed as row, column. "x, y" indexing is column, row. In this context, numpy.gradient is basically going to return dy, dx and not dx, dy.

    Try changing the line:

    dx, dy = np.gradient(zi)
    

    to:

    dy, dx = np.gradient(zi)
    

    Also, if your depths are defined as positive-up, it should be:

    dy, dx = np.gradient(-zi)
    

    However, I'm assuming you have positive-down depth conventions, so I'll leave that part of of the examples below. (So higher values are assumed to be deeper/lower in the example data below, and water will flow towards the high values.)

    Reproducing the problem

    For example, if we modify the code you gave to use random data and fill in a few variables that are coming from outside the scope of your code sample (so that it's a stand-alone example):

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.mlab import griddata
    
    # Generate some reproducible but random data
    np.random.seed(1981)
    width, height = 200, 300
    x, y, z = np.random.random((3,10))
    x *= width
    y *= height
    
    #create a list of x, y coordinate tuples
    points = zip(x, y)
    
    #create a grid on which to interpolate data
    xi, yi = np.linspace(0, width, width), np.linspace(0, height, height)
    xi, yi = np.meshgrid(xi, yi)
    
    #interpolate the data with the matlab griddata function
    zi = griddata(x, y, z, xi, yi, interp='nn')
    
    #create a matplotlib figure and adjust the width and heights
    fig = plt.figure()
    
    #create a single subplot, just takes over the whole figure if only one is specified
    ax = fig.add_subplot(111, frameon=False, xticks=[], yticks=[])
    
    #create the contours
    CS = plt.contour(xi, yi, zi, linewidths=1, colors='b')
    
    #add a streamplot
    dx, dy = np.gradient(zi)
    plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3)
    
    plt.show()
    

    The result will look like this: enter image description here

    Notice that there are lots of places where the flow lines are not perpendicular to the contours. That's an even easier indicator than the incorrect direction of the arrows that something is going wrong. (Though "perpendicular" assumes an aspect ratio of 1 for the plot, which isn't quite true for these plots unless you set it.)

    Fixing the problem

    If we just change the line

    dx, dy = np.gradient(zi)
    

    to:

    dy, dx = np.gradient(zi)
    

    We'll get the correct result:

    enter image description here


    Interpolation suggestions

    On a side note, griddata is a poor choice in this case.

    First, it's not a "smooth" interpolation method. It uses delaunay triangulation, which makes "sharp" ridges at triangle boundaries. This leads to anomalous gradients in those locations.

    Second, it limits interpolation to the convex hull of your data points, which may or may not be a good choice.

    A radial basis function (or any other smooth interpolant) is a much better choice for interpolation.

    As an example, if we modify your code snippet to use an RBF:

    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.interpolate import Rbf
    
    # Generate data
    np.random.seed(1981)
    width, height = 200, 300
    x, y, z = np.random.random((3,10))
    x *= width
    y *= height
    
    #create a grid on which to interpolate data
    xi, yi = np.mgrid[0:width:1j*width, 0:height:1j*height]
    
    #interpolate the data with the matlab griddata function
    interp = Rbf(x, y, z, function='linear')
    zi = interp(xi, yi)
    
    #create a matplotlib figure and adjust the width and heights
    fig, ax = plt.subplots(subplot_kw=dict(frameon=False, xticks=[], yticks=[]))
    
    #create the contours and streamplot
    CS = plt.contour(xi, yi, zi, linewidths=1, colors='b')
    dy, dx = np.gradient(zi.T)
    plt.streamplot(xi[:,0], yi[0,:], dx, dy, color='c', density=1, arrowsize=3)
    
    plt.show()
    

    enter image description here

    (You'll notice the intersections are not quite perpendicular due to the non-equal aspect ratio of the plot. They're all 90 degrees if we set the aspect ratio of the plot to 1, however.)

    As a side-by-side comparison of the two methods:

    enter image description here

提交回复
热议问题