contour lines from the edge of a map don't show up on basemap - matplotlib

I'm drawing several contour lines over a basemap projection as shown in the following figure:.
There are 3 contours that are not drawn completely (in Oregon, Washington and California) and seems like there is this line that has cut all 3 of them in the same latitude. I'm not sure how to solve this problem.
I added the number of interpolation points, didn't help. changed the ll and ur points to include more area didn't help.
The code is below (not reproducible but might help):
def visualise_bigaus(mus, sigmas, corxys , output_type='pdf', **kwargs):
lllat = 24.396308
lllon = -124.848974
urlat = 49.384358
urlon = -66.885444
fig = plt.figure(figsize=(4, 2.5))
ax = fig.add_subplot(111, axisbg='w', frame_on=False)
m = Basemap(llcrnrlat=lllat,
urcrnrlat=urlat,
llcrnrlon=lllon,
urcrnrlon=urlon,
resolution='i', projection='cyl')
m.drawmapboundary(fill_color = 'white')
#m.drawcoastlines(linewidth=0.2)
m.drawcountries(linewidth=0.2)
m.drawstates(linewidth=0.2, color='lightgray')
#m.fillcontinents(color='white', lake_color='#0000ff', zorder=2)
#m.drawrivers(color='#0000ff')
m.drawlsmask(land_color='gray',ocean_color="#b0c4de", lakes=True)
lllon, lllat = m(lllon, lllat)
urlon, urlat = m(urlon, urlat)
mlon, mlat = m(*(mus[:,1], mus[:,0]))
numcols, numrows = 1000, 1000
X = np.linspace(mlon.min(), urlon, numcols)
Y = np.linspace(lllat, urlat, numrows)
X, Y = np.meshgrid(X, Y)
m.scatter(mlon, mlat, s=0.2, c='red')
shp_info = m.readshapefile('./data/us_states_st99/st99_d00','states',drawbounds=True, zorder=0)
printed_names = []
ax = plt.gca()
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
for spine in ax.spines.itervalues():
spine.set_visible(False)
for k in xrange(mus.shape[0]):
#here x is longitude and y is latitude
#apply softplus to sigmas (to make them positive)
sigmax=np.log(1 + np.exp(sigmas[k][1]))
sigmay=np.log(1 + np.exp(sigmas[k][0]))
mux=mlon[k]
muy=mlat[k]
corxy = corxys[k]
#apply the soft sign
corxy = corxy / (1 + np.abs(corxy))
#now given corxy find sigmaxy
sigmaxy = corxy * sigmax * sigmay
#corxy = 1.0 / (1 + np.abs(sigmaxy))
Z = mlab.bivariate_normal(X, Y, sigmax=sigmax, sigmay=sigmay, mux=mux, muy=muy, sigmaxy=sigmaxy)
#Z = maskoceans(X, Y, Z)
con = m.contour(X, Y, Z, levels=[0.02], linewidths=0.5, colors='darkorange', antialiased=True)
'''
num_levels = len(con.collections)
if num_levels > 1:
for i in range(0, num_levels):
if i != (num_levels-1):
con.collections[i].set_visible(False)
'''
contour_labels = False
if contour_labels:
plt.clabel(con, [con.levels[-1]], inline=True, fontsize=10)
'''
world_shp_info = m.readshapefile('./data/CNTR_2014_10M_SH/Data/CNTR_RG_10M_2014','world',drawbounds=False, zorder=100)
for shapedict,state in zip(m.world_info, m.world):
if shapedict['CNTR_ID'] not in ['CA', 'MX']: continue
poly = MplPolygon(state,facecolor='gray',edgecolor='gray')
ax.add_patch(poly)
'''
if iter:
iter = str(iter).zfill(3)
else:
iter = ''
plt.tight_layout()
plt.savefig('./maps/video/gaus_' + iter + '.' + output_type, frameon=False, dpi=200)

The problem is the meshgrid not covering the complete map. The meshgrid simply doesn't have any points at the positions where you want to draw the gaussian contour line.
An example to reproduce this behaviour is the following, where the meshgrid in x directio starts at -1, such that points lower than that are not drawn.
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import numpy as np
fig, ax=plt.subplots()
ax.plot([-2,2],[-2,-2], alpha=0)
X,Y = np.meshgrid(np.linspace(-1,2),np.linspace(-2,2))
Z = mlab.bivariate_normal(X, Y, sigmax=1., sigmay=1., mux=0.1, muy=0.1, sigmaxy=0)
con = ax.contour(X, Y, Z, levels=[Z.max()/3, Z.max()/2., Z.max()*0.8],colors='darkorange')
plt.show()
A similar problem occurs in the code from the question.
While in Y direction, you use the complete map, Y = np.linspace(lllat, urlat, numrows), in X direction you restrict the mesh to start at mlon.min(),
X = np.linspace(mlon.min(), urlon, numcols)
The solution would of course be not to start the mesh in Portland, but somewhere in the ocean, i.e. at the edge of the shown map.

Related

Matplotlib: different scale on negative side of the axis

Background
I am trying to show three variables on a single plot. I have connected the three points using lines of different colours based on some other variables. This is shown here
Problem
What I want to do is to have a different scale on the negative x-axis. This would help me in providing positive x_ticks, different axis label and also clear and uncluttered representation of the lines on left side of the image
Question
How to have a different positive x-axis starting from 0 towards negative direction?
Have xticks based on data plotted in that direction
Have a separate xlabel for this new axis
Additional information
I have checked other questions regarding inclusion of multiple axes e.g. this and this. However, these questions did not serve the purpose.
Code Used
font_size = 20
plt.rcParams.update({'font.size': font_size})
fig = plt.figure()
ax = fig.add_subplot(111)
#read my_data from file or create it
for case in my_data:
#Iterating over my_data
if condition1 == True:
local_linestyle = '-'
local_color = 'r'
local_line_alpha = 0.6
elif condition2 == 1:
local_linestyle = '-'
local_color = 'b'
local_line_alpha = 0.6
else:
local_linestyle = '--'
local_color = 'g'
local_line_alpha = 0.6
datapoint = [case[0], case[1], case[2]]
plt.plot(datapoint[0], 0, color=local_color)
plt.plot(-datapoint[2], 0, color=local_color)
plt.plot(0, datapoint[1], color=local_color)
plt.plot([datapoint[0], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
plt.plot([-datapoint[2], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
plt.show()
exit()
You can define a custom scale, where values below zero are scaled differently than those above zero.
import numpy as np
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
from matplotlib.ticker import FuncFormatter
class AsymScale(mscale.ScaleBase):
name = 'asym'
def __init__(self, axis, **kwargs):
mscale.ScaleBase.__init__(self)
self.a = kwargs.get("a", 1)
def get_transform(self):
return self.AsymTrans(self.a)
def set_default_locators_and_formatters(self, axis):
# possibly, set a different locator and formatter here.
fmt = lambda x,pos: "{}".format(np.abs(x))
axis.set_major_formatter(FuncFormatter(fmt))
class AsymTrans(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
def __init__(self, a):
mtransforms.Transform.__init__(self)
self.a = a
def transform_non_affine(self, x):
return (x >= 0)*x + (x < 0)*x*self.a
def inverted(self):
return AsymScale.InvertedAsymTrans(self.a)
class InvertedAsymTrans(AsymTrans):
def transform_non_affine(self, x):
return (x >= 0)*x + (x < 0)*x/self.a
def inverted(self):
return AsymScale.AsymTrans(self.a)
Using this you would provide a scale parameter a that scales the negative part of the axes.
# Now that the Scale class has been defined, it must be registered so
# that ``matplotlib`` can find it.
mscale.register_scale(AsymScale)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([-2, 0, 5], [0,1,0])
ax.set_xscale("asym", a=2)
ax.annotate("negative axis", xy=(.25,0), xytext=(0,-30),
xycoords = "axes fraction", textcoords="offset points", ha="center")
ax.annotate("positive axis", xy=(.75,0), xytext=(0,-30),
xycoords = "axes fraction", textcoords="offset points", ha="center")
plt.show()
The question is not very clear about what xticks and labels are desired, so I left that out for now.
Here's how to get what you want. This solution uses two twined axes object to get different scaling to the left and right of the origin, and then hides all the evidence:
import matplotlib.pyplot as plt
import matplotlib as mpl
from numbers import Number
tickkwargs = {m+k:False for k in ('bottom','top','left','right') for m in ('','label')}
p = np.zeros((10, 3, 2))
p[:,0,0] -= np.arange(10)*.1 + .5
p[:,1,1] += np.repeat(np.arange(5), 2)*.1 + .3
p[:,2,0] += np.arange(10)*.5 + 2
fig = plt.figure(figsize=(8,6))
host = fig.add_subplot(111)
par = host.twiny()
host.set_xlim(-6, 6)
par.set_xlim(-1, 1)
for ps in p:
# mask the points with negative x values
ppos = ps[ps[:,0] >= 0].T
host.plot(*ppos)
# mask the points with positive x values
pneg = ps[ps[:,0] <= 0].T
par.plot(*pneg)
# hide all possible ticks/notation text that could be set by the second x axis
par.tick_params(axis="both", **tickkwargs)
par.xaxis.get_offset_text().set_visible(False)
# fix the x tick labels so they're all positive
host.set_xticklabels(np.abs(host.get_xticks()))
fig.show()
Output:
Here's what the set of points p I used in the code above look like when plotted normally:
fig = plt.figure(figsize=(8,6))
ax = fig.gca()
for ps in p:
ax.plot(*ps.T)
fig.show()
Output:
The method of deriving a class of mscale.ScaleBase as shown in other answers may be too complicated for your purpose.
You can pass two scale transform functions to set_xscale or set_yscale, something like the following.
def get_scale(a=1): # a is the scale of your negative axis
def forward(x):
x = (x >= 0) * x + (x < 0) * x * a
return x
def inverse(x):
x = (x >= 0) * x + (x < 0) * x / a
return x
return forward, inverse
fig, ax = plt.subplots()
forward, inverse = get_scale(a=3)
ax.set_xscale('function', functions=(forward, inverse)) # this is for setting x axis
# do plotting
More examples can be found in this doc.

how to plot gradient fill on the 3d bars in matplotlib

Right now there're some statistics plotted in 3d bar over (x, y). each bar height represents the density of the points in side the square grid of (x,y) plane. Right now, i can put different color on each bar. However, I want to put progressive color on the 3d bar, similar as the cmap, so the bar will be gradient filled depending on the density.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# height of the bars
z = np.ones((4, 4)) * np.arange(4)
# position of the bars
xpos, ypos = np.meshgrid(np.arange(4), np.arange(4))
xpos = xpos.flatten('F')
ypos = ypos.flatten('F')
zpos = np.zeros_like(xpos)
dx = 0.5 * np.ones_like(zpos)
dy = dx.copy()
dz = z.flatten()
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color='b', zsort='average')
plt.show()
Output the above code:
Let me first say that matplotlib may not be the tool of choice when it comes to sophisticated 3D plots.
That said, there is no built-in method to produce bar plots with differing colors over the extend of the bar.
We therefore need to mimic the bar somehow. A possible solution can be found below. Here, we use a plot_surface plot to create a bar that contains a gradient.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection= Axes3D.name)
def make_bar(ax, x0=0, y0=0, width = 0.5, height=1 , cmap="viridis",
norm=matplotlib.colors.Normalize(vmin=0, vmax=1), **kwargs ):
# Make data
u = np.linspace(0, 2*np.pi, 4+1)+np.pi/4.
v_ = np.linspace(np.pi/4., 3./4*np.pi, 100)
v = np.linspace(0, np.pi, len(v_)+2 )
v[0] = 0 ; v[-1] = np.pi; v[1:-1] = v_
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
xthr = np.sin(np.pi/4.)**2 ; zthr = np.sin(np.pi/4.)
x[x > xthr] = xthr; x[x < -xthr] = -xthr
y[y > xthr] = xthr; y[y < -xthr] = -xthr
z[z > zthr] = zthr ; z[z < -zthr] = -zthr
x *= 1./xthr*width; y *= 1./xthr*width
z += zthr
z *= height/(2.*zthr)
#translate
x += x0; y += y0
#plot
ax.plot_surface(x, y, z, cmap=cmap, norm=norm, **kwargs)
def make_bars(ax, x, y, height, width=1):
widths = np.array(width)*np.ones_like(x)
x = np.array(x).flatten()
y = np.array(y).flatten()
h = np.array(height).flatten()
w = np.array(widths).flatten()
norm = matplotlib.colors.Normalize(vmin=0, vmax=h.max())
for i in range(len(x.flatten())):
make_bar(ax, x0=x[i], y0=y[i], width = w[i] , height=h[i], norm=norm)
X, Y = np.meshgrid([1,2,3], [2,3,4])
Z = np.sin(X*Y)+1.5
make_bars(ax, X,Y,Z, width=0.2, )
plt.show()

How to create a swarm plot with matplotlib

I know the question is not very informative.. but as I do not know the name of his type of plot, I can not be more informative..
[EDIT] I changed the title, and now it is more informative...
You can do something similar with seaborn.swarmplot. I also use seaborn.boxplot (with the whiskers and caps turned off) to plot the mean and range:
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("whitegrid")
tips = sns.load_dataset("tips")
ax = sns.swarmplot(x="day", y="total_bill", data=tips)
ax = sns.boxplot(x="day", y="total_bill", data=tips,
showcaps=False,boxprops={'facecolor':'None'},
showfliers=False,whiskerprops={'linewidth':0})
plt.show()
If (for whatever reason) you don't want to use seaborn, you can have a go at making them yourself (see e.g. this explanation: https://www.flerlagetwins.com/2020/11/beeswarm.html ).
A simple version is:
#!/usr/bin/env python3
import matplotlib.pyplot as plt
import numpy as np
def simple_beeswarm(y, nbins=None):
"""
Returns x coordinates for the points in ``y``, so that plotting ``x`` and
``y`` results in a bee swarm plot.
"""
y = np.asarray(y)
if nbins is None:
nbins = len(y) // 6
# Get upper bounds of bins
x = np.zeros(len(y))
ylo = np.min(y)
yhi = np.max(y)
dy = (yhi - ylo) / nbins
ybins = np.linspace(ylo + dy, yhi - dy, nbins - 1)
# Divide indices into bins
i = np.arange(len(y))
ibs = [0] * nbins
ybs = [0] * nbins
nmax = 0
for j, ybin in enumerate(ybins):
f = y <= ybin
ibs[j], ybs[j] = i[f], y[f]
nmax = max(nmax, len(ibs[j]))
f = ~f
i, y = i[f], y[f]
ibs[-1], ybs[-1] = i, y
nmax = max(nmax, len(ibs[-1]))
# Assign x indices
dx = 1 / (nmax // 2)
for i, y in zip(ibs, ybs):
if len(i) > 1:
j = len(i) % 2
i = i[np.argsort(y)]
a = i[j::2]
b = i[j+1::2]
x[a] = (0.5 + j / 3 + np.arange(len(b))) * dx
x[b] = (0.5 + j / 3 + np.arange(len(b))) * -dx
return x
fig = plt.figure(figsize=(2, 4))
fig.subplots_adjust(0.2, 0.1, 0.98, 0.99)
ax = fig.add_subplot(1, 1, 1)
y = np.random.gamma(20, 10, 100)
x = simple_beeswarm(y)
ax.plot(x, y, 'o')
fig.savefig('bee.png')

Matplotlib multiple animate multiple lines

I have been researching on how to animate multiple lines for a flight path. The object it that I read multiple GPS files time sync them them animate each path with respect to time. I found how to animate one line using append in the animate functions. Now I need to add a second and third for as many files are imported.
I know the problem is somewhere in how I perform the set_data with the lines. I ahve seen multiple example but I do not understand what structure is required to set up multiple lines. Yes I am a newbie.
fig = plt.figure()
ax1 = plt.axes(xlim=(-108, -104), ylim=(31,34))
line, = ax1.plot([], [], lw=2)
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plotlays, plotcols = [2], ["black","red"]
lines = []
for index in range(2):
lobj = ax1.plot([],[],lw=2,color=plotcols[index])[0]
lines.append(lobj)
def init():
for line in lines:
line.set_data([],[])
return lines
x1,y1 = [],[]
x2,y2 = [],[]
frame_num = len(gps_data[0])
# animation function. This is called sequentially
def animate(i):
x = gps_data[0][i,3]
y = gps_data[0][i,2]
x1.append(x)
y1.append(y)
x = gps_data[1][i,3]
y = gps_data[1][i,2]
x2.append(x)
y2.append(y)
#X = np.array(x1, x2)
#Y = np.array(y1, y2)
#for index in range(0,1):
for lnum,line in enumerate(lines):
line.set_data([x1,y1, x2,y2])
return lines,
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=frame_num, interval=1, blit=True)
plt.show()
The Matplotlib documentation for the line2d artist explains how set_data works. It "ACCEPTS: 2D array (rows are x, y) or two 1D arrays." It also works with lists. You've given it a four element list instead. You need to set the x and y data of each line separately. I've included an example with fake data below.
import matplotlib.pyplot as plt
from matplotlib import animation
from numpy import random
fig = plt.figure()
ax1 = plt.axes(xlim=(-108, -104), ylim=(31,34))
line, = ax1.plot([], [], lw=2)
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plotlays, plotcols = [2], ["black","red"]
lines = []
for index in range(2):
lobj = ax1.plot([],[],lw=2,color=plotcols[index])[0]
lines.append(lobj)
def init():
for line in lines:
line.set_data([],[])
return lines
x1,y1 = [],[]
x2,y2 = [],[]
# fake data
frame_num = 100
gps_data = [-104 - (4 * random.rand(2, frame_num)), 31 + (3 * random.rand(2, frame_num))]
def animate(i):
x = gps_data[0][0, i]
y = gps_data[1][0, i]
x1.append(x)
y1.append(y)
x = gps_data[0][1,i]
y = gps_data[1][1,i]
x2.append(x)
y2.append(y)
xlist = [x1, x2]
ylist = [y1, y2]
#for index in range(0,1):
for lnum,line in enumerate(lines):
line.set_data(xlist[lnum], ylist[lnum]) # set data for each line separately.
return lines
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=frame_num, interval=10, blit=True)
plt.show()
fig, ax1 = plt.subplots(figsize=(20,10))
plt.xticks()
ax1.set_xlabel("time")
ax1.set_ylabel("amp")
ax1.grid(True)
ox = get_data_o("/tmp/bf_ox.npy")
oy = get_data_o("/tmp/bf_oy.npy")
graphic_data = []
graphic_data.append(ax1.plot(ox, oy,"b-")[0])
sx = get_data_o("/tmp/bf_sx.npy")
i = 1
for p in sp.procs:
c = get_c(i)
sy = get_data_o("/tmp/bf_" + p + ".npy")
graphic_data.append(ax1.plot(sx, sy,c + "-")[0])
i = i + 1
def animate(i):
print ("frame")
ox = get_data_o("/tmp/bf_ox.npy")
oy = get_data_o("/tmp/bf_oy.npy")
i = 0
graphic_data[i].set_xdata(ox)
graphic_data[i].set_ydata(oy)
i = 1
sx = get_data_o("/tmp/bf_sx.npy")
for p in sp.procs:
sy = get_data_o("/tmp/bf_" + p + ".npy")
graphic_data[i].set_xdata(sx)
graphic_data[i].set_ydata(sy)
i = i + 1
return graphic_data
ani = animation.FuncAnimation(fig, animate, interval=2000, blit=True)
plt.show()

How to drop connecting lines where the function is discontinuous

I'm plotting some functions that have several discontinuities. Each function is given as a list. I want to connect points with lines only where the function is continuous.
Here is a simplified example of what plot is doing.
x=linspace(0,1,100)
y=zeros(100)
y[x<0.5] = x[x<0.5]
y[x>=0.5] = 1 + x[x>=0.5]
plot(x, y, '-o')
There is a discontinuity at x=0.5, but plot connects all points with lines regardless.
My functions are different of course. They typically have several discontinuities in different places. The criterion for the discontinuity is simple. Say, if the function jumps by more than 0.5, I assume it is discontinuous at that point.
Is there an option in plot to tell it to drop the connecting lines between the points where the function is discontinuous? I recall being able to do that easily with gnuplot.
use nan to break the line into multiple segments:
import numpy as np
from pylab import *
x=linspace(0,1,100)
y=zeros(100)
y[x<0.5] = x[x<0.5]
y[x>=0.5] = 1 + x[x>=0.5]
pos = np.where(np.abs(np.diff(y)) >= 0.5)[0]
x[pos] = np.nan
y[pos] = np.nan
plot(x, y, '-o')
Edit:
to insert nan at discontinuities:
pos = np.where(np.abs(np.diff(y)) >= 0.5)[0]+1
x = np.insert(x, pos, np.nan)
y = np.insert(y, pos, np.nan)
Here is my suggestion for plotting tan(x):
import matplotlib.pyplot as plt
from math import *
x_lim = 3*pi/2
y_lim = 5
n = 1000
X = []
Y = []
Z = []
for i in range(0,2*n):
x = -x_lim + i*x_lim/n
y = tan(x)
if y<y_lim and y>-y_lim:
X.append(x)
Y.append(y)
else:
if len(X)>0 and len(Y)>0:
Z.append([X,Y])
del X,Y
X = []
Y = []
for i in range(0, len(Z)):
plt.plot(Z[i][0],Z[i][1])
plt.grid(True)
plt.show()