Exclude Cartopy elements from Matplotlib legend - python

I'm plotting scatter points onto a map and seeing unwanted rectangles in my legend, despite the insertion of label='_nolegend_':
# import functions
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.img_tiles as cimgt
# Create a Stamen terrain background instance
stamen_terrain = cimgt.Stamen('terrain-background')
fig = plt.figure(figsize = (10,10))
ax = fig.add_subplot(1, 1, 1, projection=stamen_terrain.crs, label='_nolegend_')
# Set range of map, stipulate zoom level
ax.set_extent([-122.7, -121.5, 37.15, 38.15], crs=ccrs.Geodetic())
ax.add_image(stamen_terrain, 12, label='_nolegend_')
# Add scatter point
ax.scatter(-122.4194, 37.7749, s=55, c='k', transform=ccrs.PlateCarree())
ax.legend(('','','San Francisco'), loc = 3)
plt.show()
How to remove the rectangles, and just show the scatter point in the legend?

The problem is that you set labels for each of the elements in the axes via ('','','San Francisco'). Instead just set the label to the scatter itself
ax.scatter(..., label="Some City")
ax.legend(loc=3)
Alternatively, if you don't want to give the scatter a label, you can pass the handle and label to the legend:
sc = ax.scatter(...)
ax.legend(handles=[sc], labels=['Some City'], loc = 3)

Related

How to zoom into a specific latitude in cartopy.crs.Orthographic?

I'm unsure if this is possible, but I'm essentially trying to isolate the Arctic circle latitude (60N) in an orthographic map AND maintain the ellipsoid, not have the zoomed in image be a rectangle/square.
Here is what I have:
fig = plt.figure(figsize=[20, 10])
ax1 = plt.subplot(1, 1, 1, projection=ccrs.Orthographic(0, 90))
for ax in [ax1]:
ax.coastlines(zorder=2)
ax.stock_img()
ax.gridlines()
This gives the north polar view I want, but I would like for it to stop at 60N.
Current yield
To get a zoom-in and square extent of an orthographic map, You need to plot some control points (with .scatter, for example) or specify correct extent in projection coordinates (more difficult). Here is the code to try.
import cartopy
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
fig = plt.figure(figsize=[8, 8])
lonlatproj = ccrs.PlateCarree()
my_projn = ccrs.Orthographic(central_longitude=0,central_latitude=90)
ax1 = plt.subplot(1, 1, 1, projection=my_projn)
# set `lowlat` as lower limits of latitude to plot some points
# these points will determine the plot extents of the map
lowlat = 60 + 2.8 # and get 60
lons, lats = [-180,-90,0,90], [lowlat,lowlat,lowlat,lowlat]
# plot invisible points to get map extents
ax1.scatter(lons, lats, s=0, color='r', transform=lonlatproj)
#ax1.stock_img() #uncomment to get it plotted
ax1.coastlines(lw=0.5, zorder=2)
ax1.gridlines(lw=2, ec='black', draw_labels=True)
Method 2: By specifying correct extent in projection coordinates
import cartopy
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
fig = plt.figure(figsize=[8, 8])
lonlatproj = ccrs.PlateCarree()
my_projn = ccrs.Orthographic(central_longitude=0,central_latitude=90)
ax1 = plt.subplot(1, 1, 1, projection=my_projn)
# These 2 lines of code grab extents in projection coordinates
_, y_min = my_projn.transform_point(0, 60, lonlatproj) #(0.0, -3189068.5)
x_max, _ = my_projn.transform_point(90, 60, lonlatproj) #(3189068.5, 0)
# prep extents of the axis to plot map
pad = 25000
xmin,xmax,ymin,ymax = y_min-pad, x_max+pad, y_min-pad, x_max+pad
# set extents with prepped values
ax1.set_extent([xmin,xmax,ymin,ymax], crs=my_projn) # data/projection coordinates
ax1.stock_img()
ax1.coastlines(lw=0.5, zorder=2)
# plot other layers of data here using proper values of zorder
# finally, plot gridlines
ax1.gridlines(draw_labels=True, x_inline=False, y_inline=True,
color='k', linestyle='dashed', linewidth=0.5)
plt.show()
Method 3 Plot the map with circular boundary
The runnable code:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import numpy as np
r_limit = 3214068.5 #from: ax.get_ylim() of above plot
# some settings
lonlatproj = ccrs.PlateCarree()
my_projn = ccrs.Orthographic(central_longitude=0, central_latitude=90)
fig = plt.figure(figsize=[8, 8])
ax = plt.subplot(1, 1, 1, projection=my_projn)
# add bluemarble image
ax.stock_img()
# add coastlines
ax.coastlines(lw=0.5, color="black", zorder=20)
# draw graticule (of meridian and parallel lines)
gls = ax.gridlines(draw_labels=True, crs=ccrs.PlateCarree(), lw=3, color="gold",
y_inline=True, xlocs=range(-180,180,30), ylocs=range(-80,91,10))
# add extra padding to the plot extents
r_extent = r_limit*1.0001
ax.set_xlim(-r_extent, r_extent)
ax.set_ylim(-r_extent, r_extent)
# Prep circular boundary
circle_path = mpath.Path.unit_circle()
circle_path = mpath.Path(circle_path.vertices.copy() * r_limit,
circle_path.codes.copy())
#set circle boundary
ax.set_boundary(circle_path)
#hide frame
ax.set_frame_on(False) #hide the rectangle frame
plt.show()

Removing legend from mpl parallel coordinates plot?

I have a parallel coordinates plot with lots of data points so I'm trying to use a continuous colour bar to represent that, which I think I have worked out. However, I haven't been able to remove the default key that is put in when creating the plot, which is very long and hinders readability. Is there a way to remove this table to make the graph much easier to read?
This is the code I'm currently using to generate the parallel coordinates plot:
parallel_coordinates(data[[' male_le','
female_le','diet','activity','obese_perc','median_income']],'median_income',colormap = 'rainbow',
alpha = 0.5)
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = mpl.cm.rainbow
bounds = [0.00,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N,)
plt.colorbar(mpl.cm.ScalarMappable(norm = norm, cmap=cmap),cax = ax, orientation = 'horizontal',
label = 'normalised median income', alpha = 0.5)
plt.show()
Current Output:
I want my legend to be represented as a color bar, like this:
Any help would be greatly appreciated. Thanks.
You can use ax.legend_.remove() to remove the legend.
The cax parameter of plt.colorbar indicates the subplot where to put the colorbar. If you leave it out, matplotlib will create a new subplot, "stealing" space from the current subplot (subplots are often referenced to by ax in matplotlib). So, here leaving out cax (adding ax=ax isn't necessary, as here ax is the current subplot) will create the desired colorbar.
The code below uses seaborn's penguin dataset to create a standalone example.
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
import numpy as np
from pandas.plotting import parallel_coordinates
penguins = sns.load_dataset('penguins')
fig, ax = plt.subplots(figsize=(10, 4))
cmap = plt.get_cmap('rainbow')
bounds = np.arange(penguins['body_mass_g'].min(), penguins['body_mass_g'].max() + 200, 200)
norm = mpl.colors.BoundaryNorm(bounds, 256)
penguins = penguins.dropna(subset=['body_mass_g'])
parallel_coordinates(penguins[['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g']],
'body_mass_g', colormap=cmap, alpha=0.5, ax=ax)
ax.legend_.remove()
plt.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
ax=ax, orientation='horizontal', label='body mass', alpha=0.5)
plt.show()

Is there a way to replace a matplotlib subplot with a legend (rather than have the legend outside the subplots)?

I have a figure with 11 scatter plots as subplots. I would like the legend (same across all 11 subplots) to replace the 12th subplot. Is there a way to put the legend there and have it be the same size as the subplots?
Matplotlib scatter plot of 11 subplots
Sort of a manual approach, but here it is:
You can "remove" an axis using ax.clear() and ax.set_axis_off(). Then you can create patches with specific colors and labels, and create a legend in the desired ax based on them.
Try this:
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
# Create figure with subplots
fig, axes = plt.subplots(figsize=(16, 16), ncols=4, nrows=3, sharex=True, sharey=True)
# Plot some random data
for row in axes:
for ax in row:
ax.scatter(np.random.random(5), np.random.random(5), color='green')
ax.scatter(np.random.random(2), np.random.random(2), color='red')
ax.scatter(np.random.random(3), np.random.random(3), color='orange')
ax.set_title('some title')
# Clear bottom-right ax
bottom_right_ax = axes[-1][-1]
bottom_right_ax.clear() # clears the random data I plotted previously
bottom_right_ax.set_axis_off() # removes the XY axes
# Manually create legend handles (patches)
red_patch = mpatches.Patch(color='red', label='Red data')
green_patch = mpatches.Patch(color='green', label='Green data')
orange_patch = mpatches.Patch(color='orange', label='Orange data')
# Add legend to bottom-right ax
bottom_right_ax.legend(handles=[red_patch, green_patch, orange_patch], loc='center')
# Show figure
plt.show()
Output:

How can I make a barplot and a lineplot in the same seaborn plot with different Y axes nicely?

I have two different sets of data with a common index, and I want to represent the first one as a barplot and the second one as a lineplot in the same graph. My current approach is similar to the following.
ax = pt.a.plot(alpha = .75, kind = 'bar')
ax2 = ax.twinx()
ax2.plot(ax.get_xticks(), pt.b.values, alpha = .75, color = 'r')
And the result is similar to this
This image is really nice and almost right. My only problem is that ax.twinx() seems to create a new canvas on top of the previous one, and the white lines are clearly seen on top of the barplot.
Is there any way to plot this without including the white lines?
You can use twinx() method along with seaborn to create a seperate y-axis, one for the lineplot and the other for the barplot. To control the style of the plot (default style of seaborn is darkgrid), you can use set_style method and specify the preferred theme. If you set style=None it resets to white background without the gridlines. You can also try whitegrid. If you want to further customize the gridlines, you can do it on the axis level using the ax2.grid(False).
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
matplotlib.rc_file_defaults()
ax1 = sns.set_style(style=None, rc=None )
fig, ax1 = plt.subplots(figsize=(12,6))
sns.lineplot(data = df['y_var_1'], marker='o', sort = False, ax=ax1)
ax2 = ax1.twinx()
sns.barplot(data = df, x='x_var', y='y_var_2', alpha=0.5, ax=ax2)
You have to remove grid lines of the second axis. Add to the code ax2.grid(False). However y-ticks of the second axis will be not align to y-ticks of the first y-axis, like here:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(pd.Series(np.random.uniform(0,1,size=10)), color='g')
ax2 = ax1.twinx()
ax2.plot(pd.Series(np.random.uniform(0,17,size=10)), color='r')
ax2.grid(False)
plt.show()

Matplotlib: Different ticklabels on shared axes

I create tightly spaced subplots with shared axes using AxesGrid. This leads to overlapping tick labels where the axes meet (Figure, A). To avoid this overlap I want to remove the first tick of the lower right axes. However, the axes are shared, so the first tick label is removed on the other axes too (Figure, B).
Is there a way to show different tick labels on shared axes?
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid
fig = plt.figure()
grid = AxesGrid(fig, 111, nrows_ncols=(2, 2), share_all=True)
#grid[-1].set_xticks([0.2, 0.4, 0.6, 0.8, 1.0]) # This applies to *all* axes
plt.show()
You can get the axis handle from grid which is just a list with ax=grid[3] and then use xticks = ax.xaxis.get_major_ticks() and xticks[1].label1.set_visible(False). As a minimal example,
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid
import numpy as np
from matplotlib.cbook import get_sample_data
#Setup figure/grid
fig = plt.figure()
grid = AxesGrid(fig, 111, nrows_ncols = (2, 2), share_all=True)
#Plot some data
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
Z = np.load(f)
for i in range(4):
im = grid[i].imshow(Z)
#Set tick one of axis 3 in grid to off
ax = grid[3]
xticks = ax.xaxis.get_major_ticks()
xticks[1].label1.set_visible(False)
plt.draw()
plt.show()

Categories

Resources