How to rotate a Contextily basemap in matplotlib and Jupyter notebook - matplotlib

I am making a set of figures with subplots in Jupyter Notebook using matplotlib and geopandas. The top plots (A & B) have geospatial data and use various basemaps (aerial imagery, shaded relief, etc.).
How can I rotate the top two plots 90-degrees, so that they are elongated?
(I will need to redo gridspec layout of course, but that is easy; what I don't know how to do is: rotate the plots but keep the geographic information for basemap plotting.)
Repeatable code is below.
import pandas as pd
import geopandas as gpd
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import contextily as ctx
from shapely.geometry import Point
plt.style.use('seaborn-whitegrid')
### DUMMY DATA
long, lat = [(-118.155, -118.051, -118.08), (38.89, 39.512, 39.1)]
q, t = [(0, 70500, 21000), (0, 8000, -1200)]
df = pd.DataFrame(list(zip(q, t, lat, long)), columns =['q', 't', 'lat', 'long'])
gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df['long'], df['lat']))
gdf.crs = "EPSG:4326"
### PLOTTING
fig = plt.figure(figsize=(10,7.5), constrained_layout=True)
gs = fig.add_gridspec(3, 2)
ax1 = fig.add_subplot(gs[0:2, 0])
ax2 = fig.add_subplot(gs[0:2, 1], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[-1, :])
### PlotA
gdf.plot(ax = ax1)
ctx.add_basemap(ax1, crs='epsg:4326', source=ctx.providers.Esri.WorldShadedRelief)
ax1.set_aspect('equal')
ax1.set_title('Plot-A')
ax1.tick_params('x', labelrotation=90)
### PlotB
gdf.plot(ax = ax2)
ctx.add_basemap(ax2, crs='epsg:4326', source=ctx.providers.Esri.WorldImagery, alpha=0.5)
ax2.set_aspect('equal')
ax2.set_title('Plot-B')
ax2.tick_params('x', labelrotation=90)
### PlotC
ax3.scatter(df.q, df.t)
ax3.set_aspect('equal')
ax3.set_title('Plot-C')
ax3.set_xlabel('q')
ax3.set_ylabel('t')

Related

Flight Path by shapely LineString is not correct

I want to connect airplanes in origin (lat_1 lon_1) to dest(lat_2 lon_2). I use these data.
callsign
latitude_1
longitude_1
latitude_2
longitude_2
0
HBAL102
-4.82114
-76.3194
-4.5249
-79.0103
1
AUA1028
-33.9635
151.181
48.1174
16.55
2
ABW120
41.9659
-87.8832
55.9835
37.4958
3
CSN461
33.9363
-118.414
50.0357
8.5723
4
ETH3730
25.3864
55.4221
50.6342
5.43903
But unfortunately, I would get an incorrect result when creating LineString with shapely. I used everything like rotate and affine but it didn't correct.
Code:
cols = pd.read_csv("/content/dirct_lines.csv",sep=";")
line = cols[["callsign","latitude_1","longitude_1","latitude_2","longitude_2"]].dropna()
line['geometry'] = line.apply(lambda x: [(x['latitude_1'],
x['longitude_1']),
(x['latitude_2'],
x['longitude_2'])], axis = 1)
geoline = gpd.GeoDataFrame(line,geometry="geometry",
crs="EPSG:4326")
import matplotlib.pyplot as plt
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
ax = world.plot(figsize=(14,9),
color='white', edgecolor='black')
geoline.plot(figsize=(14,9),ax=ax,facecolor = 'lightgrey', linewidth = 1.75,
edgecolor = 'red',
alpha = 2)
plt.show()
Shapely Output:
something that was interesting for me was that when I use Matplotlib to create lines everything is correct.
Code:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(projection=ccrs.PlateCarree())
ax.stock_img()
org_lon, org_lat = cols["longitude_1"], cols["latitude_1"]
dst_lon, dst_lat = cols["longitude_2"], cols["latitude_2"]
plt.plot([org_lon, dst_lon], [org_lat, dst_lat],
color='black', linewidth=0.5, marker='_',
transform=ccrs.PlateCarree()
)
plt.savefig(f"fight_path.png",dpi=60,facecolor = None, bbox_inches = 'tight', pad_inches = None)
plt.show()
Matplotlib Output:
What is the problem?
why isn't correct by shapely?
it's just the way you are creating the geometry. Below works correctly.
import io
import geopandas as gpd
import pandas as pd
import shapely.geometry
df = pd.read_csv(
io.StringIO(
"""callsign,latitude_1,longitude_1,latitude_2,longitude_2
HBAL102,-4.82114,-76.3194,-4.5249,-79.0103
AUA1028,-33.9635,151.181,48.1174,16.55
ABW120,41.9659,-87.8832,55.9835,37.4958
CSN461,33.9363,-118.414,50.0357,8.5723
ETH3730,25.3864,55.4221,50.6342,5.43903
"""
)
)
geoline = gpd.GeoDataFrame(
geometry=[
shapely.geometry.LineString(points)
for points in zip(
gpd.points_from_xy(df["longitude_1"], df["latitude_1"]),
gpd.points_from_xy(df["longitude_2"], df["latitude_2"]),
)
],
data=df,
)
import matplotlib.pyplot as plt
world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
ax = world.plot(figsize=(14, 9), color="white", edgecolor="black")
geoline.plot(
figsize=(14, 9),
ax=ax,
facecolor="lightgrey",
linewidth=1.75,
edgecolor="red",
)
plt.show()

How to plot an kernel density estimation in seaborn scatterplot plot

I would like to plot the same as shown in the picture( but only the red part). The curve is a kernel density estimate based only on the X-values (the y-values are irrelevant and actually all 1,2 or 3. It is here just plotted like this to distinguish between red an blue. I have plotted the scatterplot, but how can I include the kernel density curve on the scatterplot? (the black dotted lines in the curve are just the quartiles and the median).
import seaborn as sns; sns.set()
import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.ticker import MaxNLocator
import matplotlib.pyplot as plt
from scipy.stats import norm
from sklearn.neighbors import KernelDensity
%matplotlib inline
# Change plotting style to ggplot
plt.style.use('ggplot')
from matplotlib.font_manager import FontProperties
X_plot = np.linspace(0, 30, 1000)[:, np.newaxis]
X1 = df[df['Zustandsklasse']==1]['Verweildauer'].values.reshape(-1,1)
X2 = df[df['Zustandsklasse']==2]['Verweildauer'].values.reshape(-1,1)
X3 = df[df['Zustandsklasse']==3]['Verweildauer'].values.reshape(-1,1)
#print(X1)
ax=sns.scatterplot(x="Verweildauer", y="CS_bandwith", data=df, legend="full", alpha=1)
kde=KernelDensity(kernel='gaussian').fit(X1)
log_dens = kde.score_samples(X_plot)
ax.plot(X_plot[:,0], np.exp(log_dens), color ="blue", linestyle="-", label="Gaussian Kernel")
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
ax.invert_yaxis()
plt.ylim(5.5, .5)
ax.set_ylabel("Zustandsklasse")
ax.set_xlabel("Verweildauer in Jahren")
handles, labels = ax.get_legend_handles_labels()
# create the legend again skipping this first entry
leg = ax.legend(handles[1:], labels[1:], loc="lower right", ncol=2, facecolor='silver', fontsize= 7)
ax.set_xticks(np.arange(0, 30, 5))
ax2 = ax.twinx()
#get the ticks at the same heights as the left axis
ax2.set_ylim(ax.get_ylim())
s=[(df["Zustandsklasse"] == t).sum() for t in range(1, 6)]
s.insert(0, 0)
print(s)
ax2.set_yticklabels(s)
ax2.set_ylim(ax.get_ylim())
ax2.set_ylabel("Anzahl Beobachtungen")
ax2.grid(False)
#plt.tight_layout()
plt.show()
Plotting target
Whats is plotted with the code above
It's much easier if you use subplots. Here is an example with seaborn's Titanic dataset:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
titanic = sns.load_dataset('titanic')
fig, ax = plt.subplots(nrows=3, sharex=True)
ax[2].set_xlabel('Age')
for i in [1, 2, 3]:
age_i = titanic[titanic['pclass'] == i]['age']
ax[i-1].scatter(age_i, [0] * len(age_i))
sns.kdeplot(age_i, ax=ax[i-1], shade=True, legend=False)
ax[i-1].set_yticks([])
ax[i-1].set_ylim(-0.01)
ax[i-1].set_ylabel('Class ' + str(i))

Seaborn boxplot custom lables aside box

I have the code segment given below, and it generates the provided boxplot. I would like to know how to add custom labels aside each box, so that the boxplot is even more digestible to the readers of my result. The expected diagram is also provided. I reckon there should be an easy way to get this done in Seaborn/Matplotlib.
What I exactly want is to add the following labels to each box (on left hand side as in shown in the example provided)
The code use to generate boxplot
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as MaxNLocator
from matplotlib import rcParams
from matplotlib.ticker import ScalarFormatter, FuncFormatter,FormatStrFormatter, EngFormatter#, mticker
%matplotlib inline
import seaborn as sns
range_stats = pd.read_csv(f'{snappy_data_dir}range_searcg_snappy_stats.csv')
data_stats_rs_txt = range_stats[range_stats['category'] == "t"]
data_stats_rs_seq = range_stats[range_stats['category'] == "s"]
fig, ax =plt.subplots(1,2)
rcParams['figure.figsize'] =8, 6
flierprops = dict(marker='x')
labels1 = ['R1', 'R2', 'R3', 'R4', 'R5']
sns.boxplot(x='Interval',y='Total',data=data_stats_rs_txt,palette='rainbow', ax=ax[0])
sns.boxplot(x='Interval',y='Total',data=data_stats_rs_seq,palette='rainbow', ax=ax[1])
ax[0].set(xlabel='Interval (s)', ylabel='query execution time (s)', title='Text format', ylim=(0, 290))
ax[1].set(xlabel='Interval (s)', ylabel='', title='Proposed format',ylim=(0, 290), yticklabels=[])
plt.savefig("range-query-corrected.svg")
plt.savefig('snappy_compressed_rangesearch.pdf')
Resulted figure:
Expected figure with labels
This might help you, although it is not a fully correct way and is not a complete solution.
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
tips = sns.load_dataset('tips')
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
sns.set_context('poster',font_scale=0.5)
sns.boxplot(x="day", y="total_bill", data=tips,palette='rainbow', ax=axes[0], zorder=0)
axes[0].text(0, 45, r"$B1$", fontsize=20, color="blue")
axes[0].text(0.9, 45, r"$B2$", fontsize=20, color="blue")
axes[0].text(2.2, 45, r"$B3$", fontsize=20, color="blue")
axes[0].text(3.1, 45, r"$B4$", fontsize=20, color="blue");
sns.boxplot(x="day", y="tip", data=tips,palette='rainbow', ax=axes[1], zorder=10)
iris = sns.load_dataset("iris")
x_var = 'species'
y_var = 'sepal_width'
x_order = ['setosa', 'versicolor', 'virginica']
labels = ['R1','R2','R3']
max_vals = iris.groupby(x_var).max()[y_var].reindex(x_order)
ax = sns.boxplot(x=x_var, y=y_var, data=iris)
for x,y,l in zip(range(len(x_order)), max_vals, labels):
ax.annotate(l, xy=[x,y], xytext=[0,5], textcoords='offset pixels', ha='center', va='bottom')

Matplotlib pdf Output

Im new to matplotlib and wont to use the graphics in Latex.
There ist a visual output as a graphic but:
Why is there no pdf output?
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os #to remove a file
import datetime
from matplotlib.backends.backend_pdf import PdfPages
#######################
Val1 = [1,2,3,4,5,6,7,8,9,9,5,5] # in kWh
Val2 = [159,77,1.716246,2,4,73,128,289,372,347,354,302] #in m³
index = ['Apr', 'Mai', 'Jun', 'Jul','Aug','Sep','Okt','Nov','Dez','Jan', 'Feb', 'Mrz']
df = pd.DataFrame({'Val1': Val1,'Val2': Val2}, index=index)
with PdfPages('aas2s.pdf') as pdf:
plt.rc('text', usetex=True)
params = {'text.latex.preamble' : [r'\usepackage{siunitx}', r'\usepackage{amsmath}']}
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = 'Liberation'
plt.rcParams.update(params)
plt.figure(figsize=(8, 6))
plt.rcParams.update({'font.size': 12})
ax = df[['Val1','Val2']].plot.bar(color=['navy','maroon'])
plt.xlabel('X Achse m')
plt.ylabel('Y Achse Taxi quer ')
plt.legend(loc='upper left', frameon=False)
plt.title('Franz jagt im komplett verwahrlosten Taxi quer durch Bayern')
plt.show()
pdf.savefig()
plt.close()
The error is called: ValueError: No such figure: None
And how do i get a second "Y" axis for the second value?
In general, savefig should be called before show. See e.g.
Matplotlib (pyplot) savefig outputs blank image
How come pyplot from Matplotlib doesn't allow you to save an image after you show it? (with some more explanation)
Second, you want to produce the plot inside the created figure, not create a new one, hence use
fig, ax = plt.subplots(figsize=...)
df.plot(..., ax=ax)
and later call the methods of the axes (object-oriented style).
In total,
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
#######################
Val1 = [1,2,3,4,5,6,7,8,9,9,5,5] # in kWh
Val2 = [159,77,1.716246,2,4,73,128,289,372,347,354,302] #in m³
index = ['Apr', 'Mai', 'Jun', 'Jul','Aug','Sep','Okt','Nov','Dez','Jan', 'Feb', 'Mrz']
df = pd.DataFrame({'Val1': Val1,'Val2': Val2}, index=index)
with PdfPages('aas2s.pdf') as pdf:
plt.rc('text', usetex=True)
params = {'text.latex.preamble' : [r'\usepackage{siunitx}', r'\usepackage{amsmath}']}
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.serif'] = 'Times New Roman'
plt.rcParams.update(params)
fig, ax = plt.subplots(figsize=(8, 6))
plt.rcParams.update({'font.size': 12})
df[['Val1','Val2']].plot.bar(color=['navy','maroon'], ax=ax)
ax.set_xlabel('X Achse m')
ax.set_ylabel('Y Achse Taxi quer ')
ax.legend(loc='upper left', frameon=False)
ax.set_title('Franz jagt im komplett verwahrlosten Taxi quer durch Bayern')
pdf.savefig()
plt.show()
plt.close()
Now if you still need to save the figure after is it being shown, you can do so by specifically using it as argument to savefig
plt.show()
pdf.savefig(fig)

Embedding small plots inside subplots in matplotlib

If you want to insert a small plot inside a bigger one you can use Axes, like here.
The problem is that I don't know how to do the same inside a subplot.
I have several subplots and I would like to plot a small plot inside each subplot.
The example code would be something like this:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
for i in range(4):
ax = fig.add_subplot(2,2,i)
ax.plot(np.arange(11),np.arange(11),'b')
#b = ax.axes([0.7,0.7,0.2,0.2])
#it gives an error, AxesSubplot is not callable
#b = plt.axes([0.7,0.7,0.2,0.2])
#plt.plot(np.arange(3),np.arange(3)+11,'g')
#it plots the small plot in the selected position of the whole figure, not inside the subplot
Any ideas?
I wrote a function very similar to plt.axes. You could use it for plotting yours sub-subplots. There is an example...
import matplotlib.pyplot as plt
import numpy as np
#def add_subplot_axes(ax,rect,facecolor='w'): # matplotlib 2.0+
def add_subplot_axes(ax,rect,axisbg='w'):
fig = plt.gcf()
box = ax.get_position()
width = box.width
height = box.height
inax_position = ax.transAxes.transform(rect[0:2])
transFigure = fig.transFigure.inverted()
infig_position = transFigure.transform(inax_position)
x = infig_position[0]
y = infig_position[1]
width *= rect[2]
height *= rect[3] # <= Typo was here
#subax = fig.add_axes([x,y,width,height],facecolor=facecolor) # matplotlib 2.0+
subax = fig.add_axes([x,y,width,height],axisbg=axisbg)
x_labelsize = subax.get_xticklabels()[0].get_size()
y_labelsize = subax.get_yticklabels()[0].get_size()
x_labelsize *= rect[2]**0.5
y_labelsize *= rect[3]**0.5
subax.xaxis.set_tick_params(labelsize=x_labelsize)
subax.yaxis.set_tick_params(labelsize=y_labelsize)
return subax
def example1():
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
rect = [0.2,0.2,0.7,0.7]
ax1 = add_subplot_axes(ax,rect)
ax2 = add_subplot_axes(ax1,rect)
ax3 = add_subplot_axes(ax2,rect)
plt.show()
def example2():
fig = plt.figure(figsize=(10,10))
axes = []
subpos = [0.2,0.6,0.3,0.3]
x = np.linspace(-np.pi,np.pi)
for i in range(4):
axes.append(fig.add_subplot(2,2,i))
for axis in axes:
axis.set_xlim(-np.pi,np.pi)
axis.set_ylim(-1,3)
axis.plot(x,np.sin(x))
subax1 = add_subplot_axes(axis,subpos)
subax2 = add_subplot_axes(subax1,subpos)
subax1.plot(x,np.sin(x))
subax2.plot(x,np.sin(x))
if __name__ == '__main__':
example2()
plt.show()
You can now do this with matplotlibs inset_axes method (see docs):
from mpl_toolkits.axes_grid.inset_locator import inset_axes
inset_axes = inset_axes(parent_axes,
width="30%", # width = 30% of parent_bbox
height=1., # height : 1 inch
loc=3)
Update: As Kuti pointed out, for matplotlib version 2.1 or above, you should change the import statement to:
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
There is now also a full example showing all different options available.
From matplotlib 3.0 on, you can use matplotlib.axes.Axes.inset_axes:
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2,2)
for ax in axes.flat:
ax.plot(np.arange(11),np.arange(11))
ins = ax.inset_axes([0.7,0.7,0.2,0.2])
plt.show()
The difference to mpl_toolkits.axes_grid.inset_locator.inset_axes mentionned in #jrieke's answer is that this is a lot easier to use (no extra imports etc.), but has the drawback of being slightly less flexible (no argument for padding or corner locations).
source: https://matplotlib.org/examples/pylab_examples/axes_demo.html
from mpl_toolkits.axes_grid.inset_locator import inset_axes
import matplotlib.pyplot as plt
import numpy as np
# create some data to use for the plot
dt = 0.001
t = np.arange(0.0, 10.0, dt)
r = np.exp(-t[:1000]/0.05) # impulse response
x = np.random.randn(len(t))
s = np.convolve(x, r)[:len(x)]*dt # colored noise
fig = plt.figure(figsize=(9, 4),facecolor='white')
ax = fig.add_subplot(121)
# the main axes is subplot(111) by default
plt.plot(t, s)
plt.axis([0, 1, 1.1*np.amin(s), 2*np.amax(s)])
plt.xlabel('time (s)')
plt.ylabel('current (nA)')
plt.title('Subplot 1: \n Gaussian colored noise')
# this is an inset axes over the main axes
inset_axes = inset_axes(ax,
width="50%", # width = 30% of parent_bbox
height=1.0, # height : 1 inch
loc=1)
n, bins, patches = plt.hist(s, 400, normed=1)
#plt.title('Probability')
plt.xticks([])
plt.yticks([])
ax = fig.add_subplot(122)
# the main axes is subplot(111) by default
plt.plot(t, s)
plt.axis([0, 1, 1.1*np.amin(s), 2*np.amax(s)])
plt.xlabel('time (s)')
plt.ylabel('current (nA)')
plt.title('Subplot 2: \n Gaussian colored noise')
plt.tight_layout()
plt.show()