Matplotlib streamplot with streamlines that don't break or end - numpy

I'd like to make a streamplot with lines that don't stop when they get too close together. I'd rather each streamline be calculated in both directions until it hits the edge of the window. The result is there'd be some areas where they'd all jumble up. But that's what I want.
I there anyway to do this in matplotlib? If not, is there another tool I can use for this that could interface with python/numpy?
import numpy as np
import matplotlib.pyplot as plt
Y,X = np.mgrid[-10:10:.01, -10:10:.01]
U, V = Y**2, X**2
plt.streamplot(X,Y, U,V, density=1)
plt.show(False)

Ok, I've figured out I can get mostly what I want by turning up the density a lot and using custom start points. I'm still interested if there is a better or alternate way to do this.
Here's my solution. Doesn't it look so much better?
import numpy as np
import matplotlib.pyplot as plt
Y,X = np.mgrid[-10:10:.01, -10:10:.01]
y,x = Y[:,0], X[0,:]
U, V = Y**2, X**2
stream_points = np.array(zip(np.arange(-9,9,.5), -np.arange(-9,9,.5)))
plt.streamplot(x,y, U,V, start_points=stream_points, density=35)
plt.show(False)
Edit: By the way, there seems to be some bug in streamplot such that start_points keyword only works if you use 1d arrays for the grid data. See Python Matplotlib Streamplot providing start points

As of Matplotlib version 3.6.0, an optional parameter broken_streamlines has been added for disabling streamline breaks.
Adding it to your snippet produces the following result:
import numpy as np
import matplotlib.pyplot as plt
Y,X = np.mgrid[-10:10:.01, -10:10:.01]
U, V = Y**2, X**2
plt.streamplot(X,Y, U,V, density=1, broken_streamlines=False)
plt.show(False)
Note
This parameter just extends the streamlines which were originally drawn (as in the question). This means that the streamlines in the modified plot above are much more uneven than the result obtained in the other answer, with custom start_points. The density of streamlines on any stream plot does not represent the magnitude of U or V at that point, only their direction. See the documentation for the density parameter of matplotlib.pyplot.streamplot for more details on how streamline start points are chosen by default, when they aren't specified by the optional start_points parameter.
For accurate streamline density, consider using matplotlib.pyplot.contour, but be aware that contour does not show arrows.
Choosing start points automatically
It may not always be easy to choose a set of good starting points automatically. However, if you know the streamfunction corresponding to the flow you wish to plot you can use matplotlib.pyplot.contour to produce a contour plot (which can be hidden from the output), and then extract a suitable starting point from each of the plotted contours.
In the following example, psi_expression is the streamfunction corresponding to the flow. When modifying this example for your own needs, make sure to update both the line defining psi_expression, as well as the one defining U and V. Ensure these both correspond to the same flow.
The density of the streamlines can be altered by changing contour_levels. Here, the contours are uniformly distributed.
import numpy as np
import matplotlib.pyplot as plt
import sympy as sy
x, y = sy.symbols("x y")
psi_expression = x**3 - y**3
psi_function = sy.lambdify((x, y), psi_expression)
Y, X = np.mgrid[-10:10:0.01, -10:10:0.01]
psi_evaluated = psi_function(X, Y)
U, V = Y**2, X**2
contour_levels = np.linspace(np.amin(psi_evaluated), np.amax(psi_evaluated), 30)
# Draw a temporary contour plot.
temp_figure = plt.figure()
contour_plot = plt.contour(X, Y, psi_evaluated, contour_levels)
plt.close(temp_figure)
points_list = []
# Iterate over each contour.
for collection in contour_plot.collections:
# Iterate over each segment in this contour.
for path in collection.get_paths():
middle_point = path.vertices[len(path.vertices) // 2]
points_list.append(middle_point)
# Reshape python list into numpy array of coords.
stream_points = np.reshape(np.array(points_list), (-1, 2))
plt.streamplot(X, Y, U, V, density=1, start_points=stream_points, broken_streamlines=False)
plt.show(False)

Related

Linear regression to fit a power-law in Python

I have two data sets index_list and frequency_list which I plot in a loglog plot by plt.loglog(index_list, freq_list). Now I'm trying to fit a power law a*x^(-b) with linear regression. I expect the curve to follow the initial curve closely but the following code seems to output a similar curve but mirrored on the y-axis.
I suspect I am using curve_fit badly.
why is this curve mirrored on the x-axis and how I can get it to properly fit my inital curve?
Using this data
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
f = open ("input.txt", "r")
index_list = []
freq_list = []
index = 0
for line in f:
split_line = line.split()
freq_list.append(int(split_line[1]))
index_list.append(index)
index += 1
plt.loglog(index_list, freq_list)
def power_law(x, a, b):
return a * np.power(x, -b)
popt, pcov = curve_fit(power_law, index_list, freq_list)
plt.plot(index_list, power_law(freq_list, *popt))
plt.show()
The code below made the following changes:
For the scipy functions to work, it is best that both index_list and freq_list are numpy arrays, not Python lists. Also, for the power not to overflow too rapidly, these arrays should be of float type (not of int).
As 0 to a negative power causes a divide-by-zero problem, it makes sense to start the index_list with 1.
Due to the powers, also for floats an overflow can be generated. Therefore, it makes sense to add bounds to curve_fit. Especially b should be limited not to cross about 50 (the highest value is about power(100000, b) giving an overflow when be.g. is100). Also setting initial values helps to direct the fitting process (p0=...).
Drawing a plot with index_list as x and power_law(freq_list, ...) as y would generate a very weird curve. It is necessary that the same x is used for the plot and for the function.
Note that calling plt.loglog() changes both axes of the plot to logarithmic. All subsequent plots on the same axes will continue to use the logarithmic scale.
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import pandas as pd
import numpy as np
def power_law(x, a, b):
return a * np.power(x, -b)
df = pd.read_csv("https://norvig.com/google-books-common-words.txt", delim_whitespace=True, header=None)
index_list = df.index.to_numpy(dtype=float) + 1
freq_list = df[1].to_numpy(dtype=float)
plt.loglog(index_list, freq_list, label='given data')
popt, pcov = curve_fit(power_law, index_list, freq_list, p0=[1, 1], bounds=[[1e-3, 1e-3], [1e20, 50]])
plt.plot(index_list, power_law(index_list, *popt), label='power law')
plt.legend()
plt.show()

Interpolate surface and find height above surface

I have a numpy array of xyz coordinates. All but one is representing ground level. I want to interpolate the ground level to a surface and find height above the Surface of one point:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
arr = np.array([[0,0,0,2,2,4,5,5,2],
[0,3,5,0,5,2,0,5,2],
[80,70,50,90,40,75,60,46,220]])
x,y,z = arr
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.show()
Im looking for the nomenclatur for this kind of problem, not a solution. I dont know what to search for to be able to make an attempt at a solution.
Solution:
#Rebuild arr without the point which is to be measured:
arr = np.array([[0,0,0,2,2,4,5,5],
[0,3,5,0,5,2,0,5],
[80,70,50,90,40,75,60,46]])
x,y,z = arr
groundlvl = scipy.interpolate.LinearNDInterpolator(list(zip(x,y)),z)
groundlvl(2,2)
#Outputs
array(76.)
Since your ground level data does not seem to be on a grid you could use
LinearNDInterpolator. It uses Delaunay triangulation and is quite robust.
Another algorithm that I can recommend is Rbf (radial basis function).
Both are available in scipy and work on n-dimensional data.
Use one of these two to interpolate the ground level and then calculate the difference to the single value.

Cartopy AzimuthalEquidistant projection: zooming into a region and coastlines

I am trying to plot some data on an AzimuthalEquidistant projection using cartopy. However, it gives me a couple of problems. First the coastlines no longer show for this type of projection. Not sure if this is my code or a Cartopy problem. I also notice that if I use a ccrs.PlateCarree() transform in the pcolormesh command the coastlines do show but then, presumably, my data is on the wrong type of prejection?
Second I would prefer if the axis boarder was circular after plotting the data, is it possible to use set_extent or some similar function to do this?
The code below should reproduce the problems, the circle shows how I would like the boarder to look.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import matplotlib.patches as mpatches
clat = 55.0
clon = -8.0
lons = np.arange(clon-15,clon+16,0.5)
lats = np.arange(clat-15,clat+16,0.5)
d = np.random.rand(lons.shape[0],lats.shape[0])
trans = ccrs.AzimuthalEquidistant(central_latitude=clat, central_longitude=clon)
ax = plt.axes(projection=trans)
ax.coastlines(resolution='10m')
CB=ax.pcolormesh(lons-0.25, lats-0.25, d.T,
cmap=plt.cm.viridis, alpha=0.5,
transform=trans)#ccrs.PlateCarree())
p1 = mpatches.Circle((clon,clat), radius=15, color='k', lw=5, fill=False,
transform=trans)
ax.add_patch(p1)
If the data you are plotting is in latitude/longitude coordinates then the correct value for the transform keyword is indeed ccrs.PlateCarree(). This is common gotcha for new users. The transform argument tells cartopy what coordinates your data are in, and is completely independent of the projection you want to plot onto.
To make the plot circular you'll need to set the boundary yourself. The Cartopy documentation have a couple of examples of this: http://scitools.org.uk/cartopy/docs/latest/examples/always_circular_stereo.html and http://scitools.org.uk/cartopy/docs/latest/examples/star_shaped_boundary.html.

Cubic spline interpolation drops out halfway

I am trying to make a cubic spline interpolation and for some reason, the interpolation drops off in the middle of it. It's very mysterious and I can't find any mention of similar occurrences anywhere online.
This is for my dissertation so I have excluded some labels etc. to keep it obscure intentionally, but all the relevant code is as follows. For context, this is an astronomy related plot.
from scipy.interpolate import CubicSpline
import numpy as np
import matplotlib.pyplot as plt
W = np.array([0.435,0.606,0.814,1.05,1.25,1.40,1.60])
sum_all = np.array([sum435,sum606,sum814,sum105,sum125,sum140,sum160])
sum_can = np.array([sumc435,sumc606,sumc814,sumc105,sumc125,sumc140,sumc160])
fall = CubicSpline(W,sum_all)
newallx=np.arange(0.435,1.6,0.001)
newally=fall(newallx)
fcan = CubicSpline(W,sum_can)
newcanx=np.arange(0.435,1.6,0.001)
newcany=fcan(newcanx)
#----plot
plt.plot(newallx,newally)
plt.plot(newcanx,newcany)
plt.plot(W,sum_all,marker='o',color='r',linestyle='')
plt.plot(W,sum_can,marker='o',color='b',linestyle='')
plt.yscale("log")
plt.ylabel("Flux S$_v$ [erg s$^-$$^1$ cm$^-$$^2$ Hz$^-$$^1$]")
plt.xlabel("Wavelength [n$\lambda$]")
plt.show()
The plot that I get from that comes out like this, with a clear gap in the interpolation:
And in case you are wondering, these are the values in the sum_all and sum_can arrays (I assume it doesn't matter, but just in case you want the numbers to plot it yourself):
sum_all:
[ 3.87282732e+32 8.79993191e+32 1.74866333e+33 1.59946687e+33
9.08556547e+33 6.70458731e+33 9.84832359e+33]
can_all:
[ 2.98381061e+28 1.26194810e+28 3.30328780e+28 2.90254609e+29
3.65117723e+29 3.46256846e+29 3.64483736e+29]
The gap happens between [0.606,1.26194810e+28] and [0.814,3.30328780e+28]. If I change the intervals from 0.001 to something higher, it's obvious that the plot doesn't actually break off but merely dips below 0 on the y-axis (but the plot is continuous). So why does it do that? Surely that's not a correct interpolation? Just looking with our eyes, that's clearly not a well-interpolated connection between those two points.
Any tips or comments would be extremely appreciated. Thank you so much in advance!
The reason for the breakdown can be better observed on a linear scale.
We see that the spline actually passes below 0, which is undefined on a log scale.
So I would suggest to first take the logarithm of the data, perform the spline interpolation on the logarithmically scaled data, and then scale back by the 10th power.
from scipy.interpolate import CubicSpline
import numpy as np
import matplotlib.pyplot as plt
W = np.array([0.435,0.606,0.814,1.05,1.25,1.40,1.60])
sum_all = np.array([ 3.87282732e+32, 8.79993191e+32, 1.74866333e+33, 1.59946687e+33,
9.08556547e+33, 6.70458731e+33, 9.84832359e+33])
sum_can = np.array([ 2.98381061e+28, 1.26194810e+28, 3.30328780e+28, 2.90254609e+29,
3.65117723e+29, 3.46256846e+29, 3.64483736e+29])
fall = CubicSpline(W,np.log10(sum_all))
newallx=np.arange(0.435,1.6,0.001)
newally=fall(newallx)
fcan = CubicSpline(W,np.log10(sum_can))
newcanx=np.arange(0.435,1.6,0.01)
newcany=fcan(newcanx)
plt.plot(newallx,10**newally)
plt.plot(newcanx,10**newcany)
plt.plot(W,sum_all,marker='o',color='r',linestyle='')
plt.plot(W,sum_can,marker='o',color='b',linestyle='')
plt.yscale("log")
plt.ylabel("Flux S$_v$ [erg s$^-$$^1$ cm$^-$$^2$ Hz$^-$$^1$]")
plt.xlabel("Wavelength [n$\lambda$]")
plt.show()

plt.imshow(Z,norm=logNorm()) gives grey outline when Z=0

Sorry for no pictures, but this code reproduces the problem:
x=np.random.randn(1000)
y=np.random.randn(1000)
h,_,_=np.histogram2d(x,y)
plt.imshow(h, norm=LogNorm(), cmap=plt.cm.Greys)
I would expect a smooth white transition from very small values to 0 values, but there seems to be a blurred border I'd like to get rid of. Is there any way to do this?
This is to be expected because values less or equal to zero are masked and then positive values are normalized. That might mean that LogNorm is not the best option for you, but if you insist on using it you can try adding the minimum positive value to the histogram. In your case it would be 1 but let's do it more general for, say, normed histograms.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
x = np.random.randn(1000)
y = np.random.randn(1000)
h, _, _ = np.histogram2d(x, y)
im = plt.imshow(h, norm=LogNorm(), cmap=plt.cm.Greys,
interpolation='bilinear')
plt.colorbar(im)
im = plt.imshow(h + np.min(h[h > 0]), norm=LogNorm(), cmap=plt.cm.Greys,
interpolation='bilinear')
plt.colorbar(im)
Note that this change won't affect bilinear interpolation but might affect other interpolation algorithms. To ensure that interpolation is not affected you would have to create a custom subclass of Normalize.
The above figures were made using matplotlib 2.0.0rc1 which applies color mapping after interpolation. If you use a previous version you will see even more artifacts in the first figure.