Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib streamplot arrows pointing the wrong way

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 flow (streamplot) is pointed uphill. I have circled the arrows that seem to be pointed the wrong direction.

The arrows toward the bottom of the map appear to be pointed the correct direction. Does anyone know why this might be?

enter image description here

And here is most of the code which generates this plot:

#create empty arrays to fill up!
x_values = []
y_values = []
z_values = []

#iterate over wells and fill the arrays with well data
for well in well_arr:
    x_values.append(well['xpos'])
    y_values.append(well['ypos'])
    z_values.append(well['value'])

#initialize numpy array as required for interpolation functions
x = np.array(x_values, dtype=np.float)
y = np.array(y_values, dtype=np.float)
z = np.array(z_values, dtype=np.float)

#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, image['width'], image['width']),
         np.linspace(0, image['height'], image['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(figsize=(image['width']/72, image['height']/72))

#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
kwargs = {}
if groundwater_contours:
    kwargs['colors'] = 'b'

CS = plt.contour(xi, yi, zi, linewidths=linewidth, **kwargs)

#add a streamplot
dx, dy = np.gradient(zi)
plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3)
like image 617
Nick Woodhams Avatar asked Jun 03 '13 13:06

Nick Woodhams


2 Answers

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

like image 116
Joe Kington Avatar answered Oct 18 '22 19:10

Joe Kington


You can specify the arrow style with arrowstyle='->'. Try both of these and see if this works for you:

plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3, 
               arrowstyle='<-')

plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3, 
               arrowstyle='->')
like image 1
Mike Müller Avatar answered Oct 18 '22 18:10

Mike Müller