How to change the position of legend in a map? - matplotlib

I've been trying to plot a shapefile over a basemap. My issue here is the placement of the legend. I wanted it to be placed outside (next to) the map.
Specifically, I am plotting the "Ecoregion" column of the shapefile which basically labels each polygon with a colour (I figured this was better than actually putting the names on each polygon). I've tried the following code and receive an error:
pip install geopandas
pip install contextily
import geopandas as gpd
import contextily as ctx
data = gpd.read_file("icemap.shp")
plt.rcParams.update({'font.size': 14})
ax = data.plot(
figsize=(12, 10),
column="Ecoregion",
cmap="tab10",
)
map = Basemap(
llcrnrlon=-50,
llcrnrlat=30,
urcrnrlon=70.0,
urcrnrlat=85.0,
resolution="i",
lat_0=39.5,
lon_0=1,
)
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
map.fillcontinents(color="lightgreen")
map.drawcoastlines()
map.drawparallels(np.arange(10,90,20),labels=[1,1,1,1])
map.drawmeridians(np.arange(-180,180,30),labels=[1,1,0,1])
plt.title("Map", fontsize=16)
The error is -
WARNING:matplotlib.legend:No handles with labels found to put in legend.
So I tried adding
legend-True
in the "ax" parentheses and removed the "ax.legend(...)", but then the legend appears on top of the map as the picture below.
Does "handle" refer to the column that is plotted? If so, I'm confused as to why I get this error. Or do I need to add another line of code?
I'd be grateful to receive some help in this.
(Attached file link: https://drive.google.com/file/d/1OfOAstBbbxiqSybpl_CQf-o47YgpbY7D/view?usp=sharing)

I would also consider Cartopy, since Basemap has been end-of-life for a long time. The resolution of your vector is also well beyond what's plotted on screen, so you could really increase performance by simplifying it a little.
But you can pass the legend keywords along when plotting the Geodataframe.
ax = data.plot(
figsize=(10, 8),
column="Ecoregion",
cmap="tab10",
legend=True,
legend_kwds=dict(bbox_to_anchor=(1.05, 1), loc='upper left'),
)

Related

Changes of the Y value of plt.text position doesn't affect to text position

See also updates below.
By using plt.text to annotate my chart I would place it below the figure:
plt.text(x=0, y=5,
s='Джерело: openbudget.gov.ua', ha='left', fontsize=9, alpha=0.7,)
When I change the x position value, it works correctly, but changes to y value does not affect to text position at all -- space between bottom edge of the chat and annotation text (arrow) doesn't change.
The question is -- why changes of the y text position value does not affect to actual text position in case of given chart (I had annotate charts this way before and it had worked fine)?
Dataframe source data:
{"year":{"0":2021,"1":2021,"2":2021,"3":2021,"4":2021,"5":2021,"6":2021,"7":2021,"8":2021,"9":2021,"10":2021,"11":2021,"12":2022,"13":2022,"14":2022,"15":2022},"month":{"0":1,"1":2,"2":3,"3":4,"4":5,"5":6,"6":7,"7":8,"8":9,"9":10,"10":11,"11":12,"12":1,"13":2,"14":3,"15":4},"zn":{"0":8403533.0,"1":13708653.3,"2":12754794.0,"3":13003063.2,"4":13799415.0,"5":24882118.4,"6":7330157.5,"7":7360882.7,"8":13355428.8,"9":12990686.3,"10":13236689.3,"11":17756139.6,"12":11600703.4,"13":13643288.9,"14":12591057.9,"15":11387693.1},"mon":{"0":"січ","1":"лют","2":"бер","3":"кві","4":"тра","5":"чер","6":"лип","7":"сер","8":"вер","9":"жов","10":"лис","11":"гру","12":"січ","13":"лют","14":"бер","15":"кві"}}
Chart code source
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
plt.style.use('seaborn')
palette = {2021: '#55A868', 2022: '#8172B2'}
fontdict = {'size': 13,}
fig, ax = plt.subplots(nrows=1, ncols=1)
ax = sns.lineplot(
x='mon', y='zn',
data=data,
hue=data['year'], palette=palette,
ci=None, linewidth=3)
ax.legend().set_title("Рік", prop=fontdict)
yk = np.array([x/1000000 for x in list(ax.get_yticks())])
ax.set_yticks(ax.get_yticks())
ax.set_yticklabels(labels=yk)
ax.set_ylabel('млрд. грн.', fontdict={'size': 11})
ax.set_xlabel(None)
ax.set_title('Витрати місцевих бюджетів на оплату праці\n'
"(дошкільна, загальна середня освіта, бюджети міст, сіл, селищ)",
fontdict={'size': 15,}
)
plt.text(x=0,
y=60, # changes to this parameter has no effect
s='Джерело: openbudget.gov.ua', ha='left', fontsize=9, alpha=0.7,)
plt.show()
Update 1
#Redox noticed me in the comment, that described behaviour cannot be reproduced and position of the text changes accordingly to changes of Y value.
In turn I try to run my code within clear VM with Ipython and found that desired text annotation would not appear at all -- I got chart only, without any annotation.
My libraries versions is:
matplotlib==3.5.2
matplotlib-inline==0.1.3
pandas==1.4.2
seaborn==0.11.2
Adding a string as an annotation can be specified flexibly by using the coordinate system of the graph. Types include data, axis, and graph area. See here for more details.
plt.text(x=0,
y=60, # changes to this parameter has no effect
s='Джерело: openbudget.gov.ua', ha='left', fontsize=9, alpha=0.7,)
If the coordinates are specified in axes:.
plt.text(x=0, y=-0.1, transform=ax.transAxes, s='Джерело: openbudget.gov.ua', ha='left', fontsize=9, alpha=0.7,)

How do I use colourmaps with variable alpha in a Seaborn kdeplot without seeing the contour lines?

Python version: 3.6.4 (Anaconda on Windows)
Seaborn: 0.8.1
Matplotlib: 2.1.2
I'm trying to create a 2D Kernel Density plot using Seaborn but I want each step in the colourmap to have a different alpha value. I had a look at this question to create a matplotlib colourmap with alpha values: Add alpha to an existing matplotlib colormap.
I have a problem in that the lines between contours are visible. The result I get is here:
I thought that I had found the answer when I found this question: Hide contour linestroke on pyplot.contourf to get only fills. I tried the method outlined in the answer (using set_edgecolor("face") but it did not work in this case. That question also seemed to be related to vector graphics formats and I am just writing out a PNG.
Here is my script:
import numpy as np
import seaborn as sns
import matplotlib.colors as cols
import matplotlib.pyplot as plt
def alpha_cmap(cmap):
my_cmap = cmap(np.arange(cmap.N))
# Set a square root alpha.
x = np.linspace(0, 1, cmap.N)
my_cmap[:,-1] = x ** (0.5)
my_cmap = cols.ListedColormap(my_cmap)
return my_cmap
xs = np.random.uniform(size=100)
ys = np.random.uniform(size=100)
kplot = sns.kdeplot(data=xs, data2=ys,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=30)
plt.savefig("example_plot.png")
Guided by some comments on this question I have tried some other methods that have been successful when this problem has come up. Based on this question (Matplotlib Contourf Plots Unwanted Outlines when Alpha < 1) I have tried altering the plot call to:
sns.kdeplot(data=xs, data2=ys,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=30,
antialiased=True)
With antialiased=True the lines between contours are replaced by a narrow white line:
I have also tried an approach similar to this question - Pyplot pcolormesh confused when alpha not 1. This approach is based on looping over the PathCollections in kplot.collections and tuning the parameters of the edges so that they become invisible. I have tried adding this code and tweaking the linewidth -
for thing in kplot.collections:
thing.set_edgecolor("face")
thing.set_linewidth(0.01)
fig.canvas.draw()
This results in a mix of white and dark lines - .
I believe that I will not be able to tune the line width to make the lines disappear because of the variable width of the contour bands.
Using both methods (antialiasing + linewidth) makes this version, which looks cool but isn't quite what I want:
I also found this question - Changing Transparency of/Remove Contour Lines in Matplotlib
This one suggests overplotting a second plot with a different number of contour levels on the same axis, like:
kplot = sns.kdeplot(data=xs, data2=ys,
ax=ax,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=30,
antialiased=True)
kplot = sns.kdeplot(data=xs, data2=ys,
ax=ax,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=35,
antialiased=True)
This results in:
This is better, and almost works. The problem here is I need variable (and non-linear) alpha throughout the colourmap. The variable banding and lines seem to be a result of the combinations of alpha when contours are plotted over each other. I also still see some clear/white lines in the result.

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.

PyPlot in Juno: Fixing axis height

I apologise if this has already been asked, I've searched long and hard on this site and couldn't find anything that worked. I'm using Julia, specifically the Juno IDE, and I am trying to use PyPlot to create my graphs. I wanted to set the y axis height when plotting, but leave the x axis variable. Here is the code I have been using to generate my plots
fig = figure()
ax = fig[:add_axes]
BEFE250 = (plot(s1, s2, lw=1.0, "-", color="b"))
ylabel("u(x,t)", size=20)
xlabel("t", size=20)
gcf()
which gives me
However, I need space in the top left corner as I am going to layer another picture on top in latex. So I need to set the y-axis height to between -3 and 3. However, if I set the axes height in PyPlot
fig = figure()
ax = fig[:add_axes]([0.1, 0.1, -3.0, 3.0])
BEFE250 = (plot(s1, s2, lw=1.0, "-", color="b"))
ylabel("u(x,t)", size=20)
xlabel("t", size=20)
gcf()
then it switches the orientation of the x-axis. If I set the axis height after running the plot, PyPlot puts the picture in a box in a legend off to the side of the main picture, and the main picture is empty? If someone could help me out it would be greatly appreciated.
Thanks for your help.
EDIT: Using xlim=(-10.,10.) and ylim=(-2.,12.) doesn't work either. PyPlot still adapts the axes to the data.
Try xlim(-10, 10) and ylim(-2, 12) after the plot command:
plot(s1, s2, lw=1.0, "-", color="b")
ylim(-3, 3)
Just try this, without the add_axes.
You probably also want LaTeX labels -- just add an L before the string, which gives a special LaTeX string from the LaTeXString package. You can either just add the L, or add $ inside too:
ylabel(L"u(x,t)", size=20)
ylabel(L"$u(x,t)$", size=20)
[The $ are necessary in certain circumstances that I forget.]
I'm not sure how good the PyPlot support is in Juno.
You might want to try this in IJulia.
By the way, is there a reason you want to layer on a separate figure in LaTeX? That might not be the best way to do it.

Change figsize in matplotlib polar contourf

I am using the following example Example to create two polar contour subplots. When I create as the pdf there is a lot of white space which I want to remove by changing figsize.
I know how to change figsize usually but I am having difficulty seeing where to put it in this code example. Any guidance or hint would be greatly appreciated.
Many thanks!
import numpy as np
import matplotlib.pyplot as plt
#-- Generate Data -----------------------------------------
# Using linspace so that the endpoint of 360 is included...
azimuths = np.radians(np.linspace(0, 360, 20))
zeniths = np.arange(0, 70, 10)
r, theta = np.meshgrid(zeniths, azimuths)
values = np.random.random((azimuths.size, zeniths.size))
#-- Plot... ------------------------------------------------
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.contourf(theta, r, values)
plt.show()
Another way to do this would be to use the figsize kwarg in your call to plt.subplots.
fig, ax = plt.subplots(figsize=(6,6), subplot_kw=dict(projection='polar')).
Those values are in inches, by the way.
You can easily just put plt.figsize(x,y) at the beginning of the code, and it will work. plt.figsize changes the size of all future plots, not just the current plot.
However, I think your problem is not what you think it is. There tends to be quite a bit of whitespace in generated PDFs unless you change options around. I usually use
plt.savefig( 'name.pdf', bbox_inches='tight', pad_inches=0 )
This gives as little whitespace as possible. bbox_inches='tight' tries to make the bounding box as small as possible, while pad_inches sets how many inches of whitespace there should be padding it. In my case I have no extra padding at all, as I add padding in whatever I'm using the figure for.