OSMnx Add Title to Graph Plot - matplotlib

I'm using the wonderful OSMnx library created by Geoff Boeing. I am plotting a street network based on one of his tutorials. Everything works perfectly. However, I would like to plot more than 40 graphs, using different centralities. Therefore, I would like to add a title with each district and centrality name to each plot. Currently, it looks like this.
Plotted OSMnx Street Network
This is what my code looks like.
def display_most_important_node(G_centralities_sorted_dict, G_dictionary, district, centrality_measure='betweenness_centrality'):
node_color = ['red' if node == G_centralities_sorted_dict[district][centrality_measure][0][0] else '#336699' for node in ox.project_graph(G_dictionary[district]).nodes()]
node_size = [40 if node == G_centralities_sorted_dict[district][centrality_measure][0][0] else 20 for node in ox.project_graph(G_dictionary[district]).nodes()]
fig, ax = ox.plot_graph(ox.project_graph(G_dictionary[district]), annotate=False, edge_linewidth=1.5, node_size=node_size, fig_height=10, node_color=node_color, node_zorder=2)
Thank you guys.

By default, the functions of the OSMnx package call plt.show() already before they return the fig and ax handles, which means you can no longer manipulate the Figure and Axes instances (my guess is that this is done to prevent distortion of the Figure after creation). This is done using a special function called save_and_show(), which is called internally. You can prevent the showing of the figure by passing the keywords show=False and close=False to the according plotting function (close=False is needed because figures that are not automatically shown are by default closed within save_and_show()). With these keywords used, fig and ax can be manipulated after the function call, but now plt.show() has to be called explicitly. Here still a complete example following the OP:
def display_most_important_node(G_centralities_sorted_dict, G_dictionary, district, centrality_measure='betweenness_centrality'):
node_color = ['red' if node == G_centralities_sorted_dict[district][centrality_measure][0][0] else '#336699' for node in ox.project_graph(G_dictionary[district]).nodes()]
node_size = [40 if node == G_centralities_sorted_dict[district][centrality_measure][0][0] else 20 for node in ox.project_graph(G_dictionary[district]).nodes()]
fig, ax = ox.plot_graph(ox.project_graph(G_dictionary[district]), annotate=False, edge_linewidth=1.5, node_size=node_size, fig_height=10, node_color=node_color, node_zorder=2, show=False, close=False)
ax.set_title('subplot title')
fig.suptitle('figure title')
plt.show()
Note that not all OSMnx functions accept the show and close keywords. For instance, plot_shape does not. Hope this helps.

Related

What does ax=ax do while creating a plot in matplotlib?

I have a DataFrame of Heart Disease patients, which has over 300 values. What I have done initially is filter the patients aging over 50. Now I am trying to plot that DF, but running on Google, I found this piece of code that helped me plotting it.
But I am not able to understand the concept of ax = ax here:
fig, ax = plt.subplots()
over_50.plot(x="age",
y="chol",
c="target",
kind="scatter",
---------> ax=ax); <---------
I want to learn the concept behind this little piece of code here. What is it doing at its core?
In this case (a single axes plot) you can do without this parameter.
But there are more complex cases, when you create subplots with
a number of axes objects (a grid).
In this case ax (the second result from plt.subplots()) is an array
of axes objects.
Then, creating each plot, you should specify in which axes this plot
is to be created.
See e.g. https://matplotlib.org/3.1.0/gallery/subplots_axes_and_figures/subplots_demo.html
and find title Stacking subplots in one direction.
It contains such example:
fig, axs = plt.subplots(2)
fig.suptitle('Vertically stacked subplots')
axs[0].plot(x, y)
axs[1].plot(x, -y)
Here:
there is created a figure composed of 2 columns,
in the first axes there is created one line plot, and in the second - another plot.
Alternative form of how to specify axes object in which particular plot
is to be created is just ax parameter, like in our code,
where you can pass one of axes objects from the current figure.

Zooming a pherical projection in matplotlib

I need to display a catalogue of galaxies projected on the sky. Not all the sky is relevant here, so I need to center an zoom on the relevant part. I am OK with more or less any projection, like Lambert, Mollweide, etc. Here are mock data and code sample, using Mollweide:
# Generating mock data
np.random.seed(1234)
(RA,Dec)=(np.random.rand(100)*60 for _ in range(2))
# Creating projection
projection='mollweide'
fig = plt.figure(figsize=(20, 10));
ax = fig.add_subplot(111, projection=projection);
ax.scatter(np.radians(RA),np.radians(Dec));
# Creating axes
xtick_labels = ["$150^{\circ}$", "$120^{\circ}$", "$90^{\circ}$", "$60^{\circ}$", "$30^{\circ}$", "$0^{\circ}$",
"$330^{\circ}$", "$300^{\circ}$", "$270^{\circ}$", "$240^{\circ}$", "$210^{\circ}$"]
labels = ax.set_xticklabels(xtick_labels, fontsize=15);
ytick_labels = ["$-75^{\circ}$", "$-60^{\circ}$", "$-45^{\circ}$", "$-30^{\circ}$", "$-15^{\circ}$",
"$0^{\circ}$","$15^{\circ}$", "$30^{\circ}$", "$45^{\circ}$", "$60^{\circ}$",
"$75^{\circ}$", "$90^{\circ}$"]
ax.set_yticklabels(ytick_labels,fontsize=15);
ax.set_xlabel("RA");
ax.xaxis.label.set_fontsize(20);
ax.set_ylabel("Dec");
ax.yaxis.label.set_fontsize(20);
ax.grid(True);
The result is the following:
I have tried various set_whateverlim, set_extent, clip_box and so on, as well as importing cartopy and passing ccrs.LambertConformal(central_longitude=...,central_latitude=...) as arguments. I was unable to get a result.
Furthermore, I would like to shift RA tick labels down, as they are difficult to read with real data. Unfortunately, ax.tick_params(pad=-5) doesn't do anything.

Matplotlib modified histograms won't display after modification

I have plotted a histogram and would like to modify it, then re-plot it. It won't plot again without redefining the Figure and Axes object definitions. I'm using Jupyter Notebook, and I'm new to matplotlib, so I don't know if this is something that I'm not understanding about matplotlib, if it's an issue with the Jupyter Notebook or something else.
Here's my 1st block of code:
"""Here's some data."""
some_data = np.random.randn(150)
"""Here I define my `Figure` and `Axes` objects."""
fig, ax = plt.subplots()
"""Then I make a histogram from them, and it shows up just fine."""
ax.hist(some_data, range=(0, 5))
plt.show()
Here's the output from my 1st block of code:
Here's my 2nd block of code:
"""Here I modify the parameter `bins`."""
ax.hist(some_data, bins=20, range=(0, 5))
"""When I try to make a new histogram, it doesn't work."""
plt.show()
My 2nd block of code generates no visible output, which is the problem.
Here's my 3rd and final block of code:
"""But it does work if I define new `Figure` and `Axes` objects.
Why is this?
How can I display new, modified plots without defining new `Figure` and/or `Axes` objects? """
new_fig, new_ax = plt.subplots()
new_ax.hist(some_data, bins=20, range=(0, 5))
plt.show()
Here's the output from my 3rd and final block of code:
Thanks in advance.
When you generate a figure or an axis, it remains accessible for rendering or display until it's used for rendering or display. Once you execute plt.show() in your first block, the ax becomes unavailable. Your 3rd block of code is showing a plot because you're regenerating the figure and axes.

Matplotlib/Seaborn: Boxplot collapses on x axis

I am creating a series of boxplots in order to compare different cancer types with each other (based on 5 categories). For plotting I use seaborn/matplotlib. It works fine for most of the cancer types (see image right) however in some the x axis collapses slightly (see image left) or strongly (see image middle)
https://i.imgur.com/dxLR4B4.png
Looking into the code how seaborn plots a box/violin plot https://github.com/mwaskom/seaborn/blob/36964d7ffba3683de2117d25f224f8ebef015298/seaborn/categorical.py (line 961)
violin_data = remove_na(group_data[hue_mask])
I realized that this happens when there are too many nans
Is there any possibility to prevent this collapsing by code only
I do not want to modify my dataframe (replace the nans by zero)
Below you find my code:
boxp_df=pd.read_csv(pf_in,sep="\t",skip_blank_lines=False)
fig, ax = plt.subplots(figsize=(10, 10))
sns.violinplot(data=boxp_df, ax=ax)
plt.xticks(rotation=-45)
plt.ylabel("label")
plt.tight_layout()
plt.savefig(pf_out)
The output is a per cancer type differently sized plot
(depending on if there is any category completely nan)
I am expecting each plot to be in the same width.
Update
trying to use the order parameter as suggested leads to the following output:
https://i.imgur.com/uSm13Qw.png
Maybe this toy example helps ?
|Cat1|Cat2|Cat3|Cat4|Cat5
|3.93| |0.52| |6.01
|3.34| |0.89| |2.89
|3.39| |1.96| |4.63
|1.59| |3.66| |3.75
|2.73| |0.39| |2.87
|0.08| |1.25| |-0.27
Update
Apparently, the problem is not the data but the length of the title
https://github.com/matplotlib/matplotlib/issues/4413
Therefore I would close the question
#Diziet should I delete it or does my issue might help other ones?
Sorry for not including the line below in the code example:
ax.set_title("VERY LONG TITLE", fontsize=20)
It's hard to be sure without data to test it with, but I think you can pass the names of your categories/cancers to the order= parameter. This forces seaborn to use/display those, even if they are empty.
for instance:
tips = sns.load_dataset("tips")
ax = sns.violinplot(x="day", y="total_bill", data=tips, order=['Thur','Fri','Sat','Freedom Day','Sun','Durin\'s Day'])

Mask cube with features

I want to plot data from a global cube, but only for a list of countries. So I select a subcube according to the countries' "bounding box".
So far so good. What I'm looking for is an easy way to mask out all points of a cube which do not fall in any of my countries (which are represented as features), so that only those points of the cube which lie within any of my features are plotted.
Any idea is greatly appreciated =)
You can achieve this directly at the plotting stage rather than masking the cube within iris. I've approached this by setting the clip path of the artist returned by pcolor. The method is to create a list of geometries from features (in this case countries from Natural Earth, they could be from a shapefile) then transform these geometries into a matplotlib path which the image can be clipped to. I'll detail this method, and hopefully this will be enough to get you started:
I first defined a function to retrieve the Shapely geometries corresponding to given country names, the geometries come from the Natural Earth 110m administrative boundaries shapefile, access through the cartopy interface.
I then defined a second function which is a wrapper around the iris.plot.pcolor function which makes the plot and clips it to the given geometries.
Now all I need to do is set up the plot as normal, but use the plotting wrapper instead of directly calling the iris.plot.pcolor function.
Here is a complete example:
import cartopy.crs as ccrs
from cartopy.io.shapereader import natural_earth, Reader
from cartopy.mpl.patch import geos_to_path
import iris
import iris.plot as iplt
import matplotlib.pyplot as plt
from matplotlib.path import Path
def get_geometries(country_names):
"""
Get an iterable of Shapely geometries corrresponding to given countries.
"""
# Using the Natural Earth feature interface provided by cartopy.
# You could use a different source, all you need is the geometries.
shape_records = Reader(natural_earth(resolution='110m',
category='cultural',
name='admin_0_countries')).records()
geoms = []
for country in shape_records:
if country.attributes['name_long'] in country_names:
try:
geoms += country.geometry
except TypeError:
geoms.append(country.geometry)
return geoms, ccrs.PlateCarree()._as_mpl_transform
def pcolor_mask_geoms(cube, geoms, transform):
path = Path.make_compound_path(*geos_to_path(geoms))
im = iplt.pcolor(cube)
im.set_clip_path(path, transform=transform)
# First plot the full map:
cube = iris.load_cube(iris.sample_data_path('air_temp.pp'))
plt.figure(figsize=(12, 6))
ax1 = plt.axes(projection=ccrs.PlateCarree())
ax1.coastlines()
iplt.pcolor(cube)
# Now plot just the required countries:
plt.figure(figsize=(12, 6))
ax2 = plt.axes(projection=ccrs.PlateCarree())
ax2.coastlines()
countries = [
'United States',
'United Kingdom',
'Saudi Arabia',
'South Africa',
'Nigeria']
geoms, transform = get_geometries(countries)
pcolor_mask_geoms(cube, geoms, transform(ax2))
plt.show()
The results of which look like this:
If you want to use iris.plot.pcolormesh instead you will need to modify the plotting function a little bit. This is dues to a workaround for a matplotlib issue that is currently included in cartopy. The modified version would look like this:
def pcolor_mask_geoms(cube, geoms, transform):
path = Path.make_compound_path(*geos_to_path(geoms))
im = iplt.pcolormesh(cube)
im.set_clip_path(path, transform=transform)
try:
im._wrapped_collection_fix.set_clip_path(path, transform)
except AttributeError:
pass