Connecting a non-linear axis in matplotlib with spatial coordinates - python

I am hoping to graph data that looks something like:
import matplotlib.pyplot as plt
x = [0, 350, 40, 55, 60]
y = [0, 20, 40, 10, 20]
plt.scatter(x,y);
Gives something like this:
However I would like to change this so the axes run from 180 to 360 and then from 0 to 180 all in the same figure. Essentially I want connect 360 to 0 in the center of the figure.

There might be something creative you can do with matplotlib.units, but I often find that interface to be quite clunky.
I'm not 100% certain the result you want, but from your description it sounds like you want a plot in cartesian coordinates with an xaxis that goes from 180 → 360 → 180. Unfortunately this is not directly doable with a single Axes in matplotlib (without playing around with the units above).
Thankfully, you can stitch together 2 plots to get the desired end result that you want:
import matplotlib.pyplot as plt
x = [0, 350, 40, 55, 60]
y = [0, 20, 40, 10, 20]
fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True, grid
spec_kw={"wspace": 0})
ax1.scatter(x, y, clip_on=False)
ax2.scatter(x, y, clip_on=False)
ax1.set_xlim(180, 360)
ax1.set_xticks([180, 240, 300, 360])
ax1.spines["right"].set_visible(False)
ax2.set_xlim(0, 180)
ax2.set_xticks([60, 120, 180])
ax2.yaxis.set_visible(False)
ax2.spines["left"].set_visible(False)
plt.show()
The trick for the above is that I actually plotted all of the data twice (.scatter(...)), laid those plots out next to eachother ({'wspace': 0}) and then limited their data view (.set_xlim) to make it appear as a seamless plot that goes from 180 → 360 → 180.
You may also be asking for a plot not in cartesian coordinates, but in polar coordinates. In that case you can use the following code:
import matplotlib.pyplot as plt
from numpy import deg2rad
x = [0, 350, 40, 55, 60]
y = [0, 20, 40, 10, 20]
fig, ax = plt.subplots(subplot_kw={"projection": "pola
r"})
ax.scatter(deg2rad(x), y)
ax.set_yticks([0, 20, 40, 60])
plt.show()

Most people would plot that as -180 to 180?
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(2, 1)
x = np.arange(0, 360, 10)
y = x * 1
y[x>180] = y[x>180] - 360
ax[0].scatter(x, np.abs(y), c=x)
ax[1].scatter(y, np.abs(y), c=x)
plt.show()

Related

How to mask data that appears in the ocean using cartopy and matplotlib

Not at all sure what I'm doing wrong besides perhaps the order that I am plotting the ocean in. I am trying to get the ocean feature in to mask the data in the ocean. I am trying to get data to not appear in the ocean and to get the ax.add_feature(cfeature.OCEAN) to be on top of the temperature data I am plotting so I see ocean and no data. Similar to what is happening in the great lakes region where you see lakes and no temperature data.
proj_map = ccrs.Mercator(central_longitude=cLon)
proj_data = ccrs.PlateCarree()
fig = plt.figure(figsize=(30,20))
ax = fig.add_subplot(1,1,1, projection=proj_map)
ax.set_extent([-84,-66,37,47.5])
CT = ax.contourf(Tlat, Tlon, tempF, transform=temp.metpy.cartopy_crs, levels=clevs,
cmap=cmap)
ax.add_feature(cfeature.COASTLINE.with_scale('10m'), linewidth=0.5)
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.LAKES)
ax.add_feature(cfeature.BORDERS, linewidth=0.5)
ax.add_feature(cfeature.STATES.with_scale('10m'), linewidth=0.5)
ax.add_feature(USCOUNTIES.with_scale('20m'), linewidth=0.25)
cbar = fig.colorbar(CT, orientation='horizontal', shrink=0.5, pad=0.05)
cbar.ax.tick_params(labelsize=14)
cbar.set_ticks([-50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100,
110, 120])
cbar.ax.set_xlabel("Temp ($^\circ$F)",fontsize=20)
Here is what the image looks like
You need to use zorder option to specify proper orders of the plot on the map. Features with largers values of zorder will be plotted on top of those with lower values. In your case, you need zorder of the OCEAN larger than the filled-contour.
Here is a runnable demo code and its sample plot. Read comments in the code for explanation.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import numpy as np
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(projection=ccrs.PlateCarree()))
extent = [-84, -66, 37, 47.5]
# generate (x, y), centered at the middle of the `extent`
mean = [(extent[0]+extent[1])/2, (extent[2]+extent[3])/2] #mean
cov = [[7, 3.5], [3.5, 6]] #co-variance matrix
x, y = np.random.multivariate_normal(mean, cov, 4000).T
# make a 2D histogram
# set the edges of the bins in x and y directions
bin_size = 40
lonrange = np.linspace(extent[0], extent[1], bin_size)
latrange = np.linspace(extent[2], extent[3], bin_size)
# the cell sizes of the bins:
dx = (lonrange[1]- lonrange[0])/2
dy = (latrange[3]- latrange[2])/2
# compute array of center points of the bins' grid
# the dimensions of mesh-grid < the edges by 1
lonrange2 = np.linspace(extent[0]+dx, extent[1]-dx, bin_size-1)
latrange2 = np.linspace(extent[2]+dy, extent[3]-dy, bin_size-1)
x2d, y2d = np.meshgrid(lonrange2, latrange2)
# create 2d-histogram
# zorder is set = 10
h = ax.hist2d(x, y, bins=[lonrange, latrange], zorder=10, alpha=0.75)
#h: (counts, xedges, yedges, image)
ax.add_feature(cfeature.OCEAN, zorder=12) #zorder > 10
ax.add_feature(cfeature.BORDERS, linewidth=0.5)
ax.gridlines(draw_labels=True, xlocs=list(range(-85, -60, 5)), ylocs=list(range(35, 50, 5)),
linewidth=1.8, color='gray', linestyle='--', alpha=0.8, zorder=20)
# plot colorbar, using image from hist2d's result
plt.colorbar(h[3], ax=ax, shrink=0.45)
# finally, show the plot.
plt.show()
The output plot:
If zorder option is not specified:
ax.add_feature(cfeature.OCEAN)
the plot will be:

How to plot a line in 3 dimensional space with matplotlib

I have two 3D-points, for example a = (100, 100, 10) and b = (0, 100, 60), and would like to fit a line through those points.
I know, the 3D line equation can have different shapes:
Vector-form:
(x,y,z)=(x0,y0,z0)+t(a,b,c)
Parameter-form:
x=x0+ta
y=y0+tb
z=z0+tc
But I have a problem getting the data in the right shape for a numerical function.
The following code should work
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(projection ='3d')
# defining coordinates for the 2 points.
x = np.array([100, 0])
y = np.array([100, 100])
z = np.array([10, 60])
# plotting
ax.plot3D(x, y, z)
plt.show()
Here the ax.plot3D() plots a curve that joins the points (x[i], y[i], z[i]) with straight lines.

Is it possible to change the frequency of ticks on a pyplot INDEPENDENT of length of data set and zoom?

When I plot data using matplotlib I always have 5-9 ticks on my x-axis independent of the range I plot, and if I zoom on the x-axis the tick spacing decreases, so I still see 5-9 ticks.
however, I would like 20-30 ticks on my x-axis!
I can achieve this with the following:
from matplotlib import pyplot as plt
import numpy as np
x = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
y = [1, 4, 3, 2, 7, 6, 9, 8, 10, 5]
number_of_ticks_on_x_axis = 20
plt.plot(x, y)
plt.xticks(np.arange(min(x), max(x)+1, (max(x) - min(x))/number_of_ticks_on_x_axis))
plt.show()
If I now zoom on the x-axis, no new ticks appear between the existing ones. I would like to still have ~20 ticks however much I zoom.
Assuming that you want to fix the no. of ticks on the X axis
...
from matplotlib.ticker import MaxNLocator
...
fig, ax = plt.subplots()
ax.xaxis.set_major_locator(MaxNLocator(15, min_n_ticks=15))
...
Please look at the docs for MaxNLocator
Example
In [36]: import numpy as np
...: import matplotlib.pyplot as plt
In [37]: from matplotlib.ticker import MaxNLocator
In [38]: fig, ax = plt.subplots(figsize=(10,4))
In [39]: ax.grid()
In [40]: ax.xaxis.set_major_locator(MaxNLocator(min_n_ticks=15))
In [41]: x = np.linspace(0, 1, 51)
In [42]: y = x*(1-x)
In [43]: plt.plot(x, y)
Out[43]: [<matplotlib.lines.Line2D at 0x7f9eab409e10>]
gives
and when I zoom into the maximum of the curve I get
You can link a callback function to an event in the canvas. In you case you can trigger a function that updates the axis when a redraw occurs.
from matplotlib import pyplot as plt
import numpy as np
x = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
y = [1, 4, 3, 2, 7, 6, 9, 8, 10, 5]
n = 20
plt.plot(x, y)
plt.xticks(np.arange(min(x), max(x)+1, (max(x) - min(x))/n), rotation=90)
def on_zoom(event):
ax = plt.gca()
fig = plt.gcf()
x_min, x_max = ax.get_xlim()
ax.set_xticks(np.linspace(x_min, x_max, n))
# had to add flush_events to get the ticks to redraw on the last update.
fig.canvas.flush_events()
fig = plt.gcf()
fig.canvas.mpl_disconnect(cid)
cid = fig.canvas.mpl_connect('draw_event', on_zoom)

Plotting a wind rose the Windrose Library

I've got wind data which includes wind speed and wind direction.
However, my wind direction is defined anti-clockwise. Meaning, 45 deg for my data is actually NW.
Is there any chance to change this using Windrose in Python?
I've got the following code to plot the Windrose:
from windrose import WindroseAxes
import matplotlib.pyplot as plt
theta = [0, 60, 120, 180, 240, 300]
speed = [10, 0, 10, 40, 50, 40]
ax = WindroseAxes.from_ax()
ax.bar(theta, speed)
plt.show()
The direction of your windrose is determined by the theta list. If 90° is not on the side you wish, you can convert all theta angles to the opposite and therefore create a mirror of your original image.
Let's imagine your original code is the following.
from windrose import WindroseAxes
import matplotlib.pyplot as plt
theta = [0, 90]
speed = [10, 10]
ax = WindroseAxes.from_ax()
ax.bar(theta, speed)
plt.show()
And this shows you a graph with a bar on the East, while you want it on the West (or the opposite).
If you take the opposite angle, you swap the graph. The following code would server your purpose.
from windrose import WindroseAxes
import matplotlib.pyplot as plt
theta = [0, 90]
theta = [360 - x for x in theta] # Take the opposite angle
speed = [10, 10]
ax = WindroseAxes.from_ax()
ax.bar(theta, speed)
plt.show()

Multiple Broken Axis On A Histogram in Matplotlib

So I've got some data which I wish to plot via a frequency density (unequal class width) histogram, and via some searching online, I've created this to allow me to do this.
import numpy as np
import matplotlib.pyplot as plt
plt.xkcd()
freqs = np.array([3221, 1890, 866, 529, 434, 494, 382, 92, 32, 7, 7])
bins = np.array([0, 5, 10, 15, 20, 30, 50, 100, 200, 500, 1000, 1500])
widths = bins[1:] - bins[:-1]
heights = freqs.astype(np.float)/widths
plt.xlabel('Cost in Pounds')
plt.ylabel('Frequency Density')
plt.fill_between(bins.repeat(2)[1:-1], heights.repeat(2), facecolor='steelblue')
plt.show()
As you may see however, this data stretches into the thousands on the x axis and on the y axis (density) goes from tiny data (<1) to vast data (>100). To solve this I will need to break both axis. The closest to help I've found so far is this, which I've found hard to use. Would you be able to help?
Thanks, Aj.
You could just use a bar plot. Setting the xtick labels to represent the bin values.
With logarithmic y scale
import numpy as np
import matplotlib.pyplot as plt
plt.xkcd()
fig, ax = plt.subplots()
freqs = np.array([3221, 1890, 866, 529, 434, 494, 382, 92, 32, 7, 7])
freqs = np.log10(freqs)
bins = np.array([0, 5, 10, 15, 20, 30, 50, 100, 200, 500, 1000, 1500])
width = 0.35
ind = np.arange(len(freqs))
rects1 = ax.bar(ind, freqs, width)
plt.xlabel('Cost in Pounds')
plt.ylabel('Frequency Density')
tick_labels = [ '{0} - {1}'.format(*bin) for bin in zip(bins[:-1], bins[1:])]
ax.set_xticks(ind+width)
ax.set_xticklabels(tick_labels)
fig.autofmt_xdate()
plt.show()

Categories

Resources