Related
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()
I am trying to figure out how to change the placement of gridline labels (more specifically, latitude labels) in cartopy when using a polar stereographic projection (NorthPolarStereo). My axis currently looks like this:
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import numpy as np
import cartopy.crs as ccrs
# Helper function
# from https://nordicesmhub.github.io/NEGI-Abisko-2019/training/example_NorthPolarStereo_projection.html
def polarCentral_set_latlim(lat_lims, ax):
ax.set_extent([-180, 180, lat_lims[0], lat_lims[1]], ccrs.PlateCarree())
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
ax.set_boundary(circle, transform=ax.transAxes)
fig = plt.figure(figsize=(6,6))
ax = fig.add_subplot(1,1,1,projection=ccrs.NorthPolarStereo(central_longitude=0))
ax.coastlines(linewidth=0.5,color='k')
ax.gridlines(color='C7',lw=1,ls=':',draw_labels=True,rotate_labels=False,ylocs=[60,70,80])
polarCentral_set_latlim((50,90),ax)
Oddly, the latitude labels are always plotted at about 150E even if the central_longitude is set to a different value. Preferably, I'd like to align them with the 180th meridian (similar to the labels in this plot) but I cannot find an option in the documentation of the gridlines function to set their position. Did I overlook something or would I have to place them manually with plt.text()
After the gridlines is created, some labels can be accessed and moved to new positions.
I use alternate method to define the circular boundary of the plot in order not to interfere with gridlines' labels.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import numpy as np
# Value for r_extent is obtained by trial and error
# get it with `ax.get_ylim()` after running this code
r_extent = 4651194.319
r_extent *= 1.005 #increase a bit for better result
# Projection settings
lonlat_proj = ccrs.PlateCarree()
use_proj = ccrs.NorthPolarStereo(central_longitude=0)
fig = plt.figure(figsize=[7, 7])
ax = plt.subplot(1, 1, 1, projection=use_proj)
ax.set_extent([-180, 180, 50, 90], lonlat_proj)
#ax.stock_img() # add bluemarble image
ax.coastlines(lw=0.5, color="black", zorder=20) # add coastlines
# draw graticule (meridian and parallel lines)
gls = ax.gridlines(draw_labels=True, crs=lonlat_proj, lw=1, color="gray",
y_inline=True, xlocs=range(-180,180,30), ylocs=range(0,90,10))
# set the plot limits
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_extent,
circle_path.codes.copy())
#set circular boundary
#this method will not interfere with the gridlines' labels
ax.set_boundary(circle_path)
ax.set_frame_on(False) #hide the boundary frame
plt.draw() # Enable the use of `gl._labels`
# Reposition the tick labels
# Labels at 150d meridian will be moved to 180d
for ea in gls._labels:
# No _labels if not run `plt.draw()`
pos = ea[2].get_position()
#print("Position:", pos[0], pos[1])
if (pos[0]==150):
ea[2].set_position([180, pos[1]])
plt.show()
Might because of the cartopy version (0.21.1) I used but the the answer by #swatchai didn't work for me. Yet a slight change can be done and will work fine.
plt.draw()
for ea in gls.label_artists:
# return ea of mpl.text type, e.g. Text(135, 30, '30°N')
pos = ea.get_position()
if pos[0] == 135:
ea.set_position([180, pos[1]])
I attempted to plot the kernel density distribution (Gaussian) curve along with the histogram plot of two data set in python.
However, in my script the estimation of 95% (data1: marked by red color vertical line) and 5% (data2: marked by black color vertical line) is very time-consuming, e.g. I need to test different limits [detail explanation in code, where I need to change the upper limited] to get the 95% and 5% probability of the kernel density curve.
May someone help out me here and suggest possible way out fixed this issue or another approach to plot the kernel density curve along with its 95% and 5% probability.
Thank you!
My script is here.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from sklearn.neighbors import KernelDensity
from scipy import stats
data1 = result['95_24'] # data 1
data2 = result['5_24'] # data 2
def plot_prob_density(data1, data2, x_start1, x_end1):
fig, (ax1) = plt.subplots(1, 1, figsize=(6,5), sharey=False)
unit = 1.5
x = np.linspace(-20, 20, 1000)[:, np.newaxis]
# Hisogram plot of data
ax1.hist(data1, bins=np.linspace(-20,20,40), density=True, color='r', alpha=0.4)
ax1.hist(data2, bins=np.linspace(-20,20,40), density=True, color='k', alpha=0.4)
# kernel density estimation
kd_data1 = KernelDensity(kernel='gaussian', bandwidth=1.8).fit(data1)
kd_data2 = KernelDensity(kernel='gaussian', bandwidth=1.8).fit(data2)
kd_vals_data1 = np.exp(kd_data1.score_samples(x))
kd_vals_data2 = np.exp(kd_data2.score_samples(x))
# density plot
ax1.plot(x, kd_vals_data1, color='r', label='$Na$', linewidth=2)
ax1.plot(x, kd_vals_data2, color='k', label='$Λ$', linewidth = 2)
# using the function get probability)
ax1.axvline(x=x_end1,color='red',linestyle='dashed', linewidth = 3, label='$β_{95\%}$')
ax1.axvline(x=x_start1,color='k',linestyle='dashed', linewidth = 3, label='$β_{5\%}$')
# Show the plots
ax1.set_ylabel('Probability density', fontsize=12)
ax1.set_xlabel('Beta', fontsize=12)
ax1.set_xlim([-20, 20])
ax1.set_ylim(0, 0.3)
ax1.set_yticks([0, 0.1, 0.2, 0.3])
ax1.set_xticks([-20, 20, -10, 10, 0])
ax1.legend(fontsize=12, loc='upper left', frameon=False)
fig.tight_layout()
gc.collect()
return kd_data1, kd_data2,
# Calculation of 95% and 5 % for data1 and data2 Kernel density curve
def get_probability(start_value, end_value, eval_points, kd):
# Number of evaluation points
N = eval_points
step = (end_value - start_value) / (N - 1) # Step size
x = np.linspace(start_value, end_value, N)[:, np.newaxis] # Generate values in the range
kd_vals = np.exp(kd.score_samples(x)) # Get PDF values for each x
probability = np.sum(kd_vals * step) # Approximate the integral of the PDF
return probability.round(4)
data1 = np.array(data1).reshape(-1, 1)
data2 = np.array(data2).reshape(-1, 1)
kd_data1, kd_data2= plot_prob_density(data1, data2, x_start1=-2.2, x_end1=5.3)
# ##############################
print('Beta-95%: {}'
.format(get_probability(start_value = -20,
end_value = 5.3,
eval_points = 1000,
kd = kd_data1)))
# here, I modify the end-value every time and then see teh output #value, when it reached to 95% then i took taht values as 95% #confidence, however this is very confsuing, i want to compute this 95% directly and same for 5% probbaility, computed below:
print('Beta-5%: {}\n'
.format(get_probability(start_value = -20,
end_value = -2.2,
eval_points = 1000,
kd = kd_data2)))
####################################################################
plt.savefig("Ev_test.png")
The pictorial representation is also attached here.
Histogram and kernel density plot along with its 95% and 5% probability limits highlighted with red and black vertical bold lines:
Here is the possible way out to fix this issue. Additionally, the proposed method it has error in percentile calculation, therefore i recommend not to use that:
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import gaussian_kde
import seaborn as sns
from sklearn.neighbors import KernelDensity
%matplotlib inline
import numpy as np
from scipy import stats
import statsmodels.api as sm
import matplotlib.pyplot as plt
from statsmodels.distributions.mixture_rvs import mixture_rvs
from scipy.stats import norm
import numpy as np
fig = plt.figure(figsize=(4, 4), dpi=300)
ax = fig.add_subplot(111)
# Plot the histogram
ax.hist(data8,bins=20,zorder=1,color="r",density=True,alpha=0.6,)
ax.hist(data7,bins=20,zorder=1,color="black",density=True,alpha=0.6,)
# kde.fit()
kde = sm.nonparametric.KDEUnivariate(data8)
kde1 = sm.nonparametric.KDEUnivariate(data7)
# Plot the KDE for various bandwidths
for bandwidth in [1.8]:
kde.fit(bw=bandwidth)
kde1.fit(bw=bandwidth)# Estimate the densities
ax.plot(kde.support, kde.density,"-",lw=2,color="r",zorder=10, alpha=0.6, label="Data1")
ax.plot(kde1.support, kde1.density,"-",lw=2,color="black",zorder=10, alpha=0.6, label="Data2")
ax.legend(loc="best")
ax.set_xlim([-20, 40])
ax.set_ylim([0, 0.3])
ax.grid(False)
# Probabilities calculation
quantiles_mesh = np.linspace(0,1,len(kde.density))
fig = plt.figure(figsize=(2, 2), dpi=300)
plt.plot(quantiles_mesh, kde.icdf)
data_1_95= np.percentile(kde1.icdf, 95)
data_2_5= np.percentile(kde2.icdf, 5)
ax.axvline(x=data_1_95,color='red',linestyle='dashed', linewidth = 2)
ax.axvline(x=data_2_5,color='k',linestyle='dashed', linewidth = 2)
#plt.savefig("KDE_Plot.png")
For this simple plot I want to enlarge the figure size but I want to keep the actual plot size. How is this possible? Until now I found just a lot of possibilities which changed both sizes together.
import matplotlib.pyplot as plt
plt.plot([-1, -4.5, 16, 23, 15, 59])
plt.show()
You can achieve a constant axes sizes by addind the axes manually.
In my code example I introduce an scale factor sc which determines the ratio of figure and axes size.
import matplotlib.pyplot as plt
gr = (1 + np.sqrt(5))/2
sc = 2
fig_w = 3 * gr * sc
fig_h = 3 * sc
fig = plt.figure(figsize=(fig_w, fig_h))
panel_width = 1/sc
panel_height = 1/sc
off = (1 - 1/sc) / 2
ax = fig.add_axes([off, off, panel_width, panel_height])
ax.plot([-1, -4.5, 16, 23, 15, 59])
I am trying to have text rotate onto a plot which is shown on log scale. When I compute the angles (based on the solution in this answer) the angles are getting incorrectly rounded to 0 or 90 degrees. This is because the angles are computed on a linear scale first, and then transformed. This calculation in linear space is the cause of the trouble. Even in a situation where I know the gradient, (either in a linear or logarithmic scale), I am not sure how I can put this onto the graph correctly.
MWE
import matplotlib as mpl
rc_fonts = {
"text.usetex": True,
'text.latex.preview': True,
"font.size": 50,
'mathtext.default': 'regular',
'axes.titlesize': 55,
"axes.labelsize": 55,
"legend.fontsize": 50,
"xtick.labelsize": 50,
"ytick.labelsize": 50,
'figure.titlesize': 55,
'figure.figsize': (10, 6.5), # 15, 9.3
'text.latex.preamble': [
r"""\usepackage{lmodern,amsmath,amssymb,bm,physics,mathtools,nicefrac,letltxmacro,fixcmex}
"""],
"font.family": "serif",
"font.serif": "computer modern roman",
}
mpl.rcParams.update(rc_fonts)
import matplotlib.pylab as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes, InsetPosition, mark_inset
import numpy as np
x = np.linspace(0, 20, 100)
y = np.exp(x**2)
g = 2*x*y # Gradient.
lg = 2 * x # Gradient on a log scale.
plt.clf()
plt.plot(x, y)
plt.yscale('log')
for x in [0,2,4,7,18]:
angle_data = np.rad2deg(np.arctan2(2 * x * np.exp(x**2), 1))
y = np.exp(x**2)
angle_screen = plt.gca().transData.transform_angles(np.array((angle_data,)), np.array([x, y]).reshape((1, 2)))[0]
plt.gca().text(x, y, r'A', rotation_mode='anchor', rotation=angle_screen, horizontalalignment='center')
plt.ylim(1e0, 1e180)
plt.xlim(-1, 20)
plt.xlabel(r'$x$')
plt.title(r'$\exp(x^2)$', y=1.05)
plt.savefig('logscale.pdf', format='pdf', bbox_inches='tight')
A few ideas?
I had tried to use the fact that for very large functions I can calculate the difference from 90 degrees using arctan(x) ~ pi/2 - arctan(1/x), and the former angle uses the low angle approximation so is just 1/x. However, after plugging this into transform_angles this is rounded incorrectly.
A slight hack of a solution
If I guess the aspect ratio of the figure (c0.6) and then also adjust for the difference in scales (x in [0:20] while log10(y) is in [0:180], giving a difference of 9 in scale), then I can get the following, although I don't think this is particularly sustainable, especially if I want to tweak something later.
# The 9 comes from tha fact that x is in [0:20], log10(y) is in [0, 180]. The factor of 0.6 is roughly the aspect ratio of the main plot shape.
plt.gca().text(x, y, r'A', rotation_mode='anchor', rotation=np.rad2deg(np.arctan(0.6 * x/9.0)), horizontalalignment='center')
I updated the solution to the original question with a class RotationAwareAnnotation2, which will be better suited here. It would first transform the points into screen coordinates, and then apply the rotation.
This this case it would look as follows.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.text as mtext
import matplotlib.transforms as mtransforms
class RotationAwareAnnotation2(mtext.Annotation):
def __init__(self, s, xy, p, pa=None, ax=None, **kwargs):
self.ax = ax or plt.gca()
self.p = p
if not pa:
self.pa = xy
kwargs.update(rotation_mode=kwargs.get("rotation_mode", "anchor"))
mtext.Annotation.__init__(self, s, xy, **kwargs)
self.set_transform(mtransforms.IdentityTransform())
if 'clip_on' in kwargs:
self.set_clip_path(self.ax.patch)
self.ax._add_text(self)
def calc_angle(self):
p = self.ax.transData.transform_point(self.p)
pa = self.ax.transData.transform_point(self.pa)
ang = np.arctan2(p[1]-pa[1], p[0]-pa[0])
return np.rad2deg(ang)
def _get_rotation(self):
return self.calc_angle()
def _set_rotation(self, rotation):
pass
_rotation = property(_get_rotation, _set_rotation)
x = np.linspace(0, 20, 100)
f = lambda x: np.exp(x**2)
y = f(x)
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set(yscale = 'log', ylim=(1e0, 1e180), xlim=(-1, 20), xlabel=r'$x$')
annots= []
for xi in [0,2,4,7,18]:
an = RotationAwareAnnotation2("A", xy=(xi,f(xi)), p=(xi+.01,f(xi+.01)), ax=ax,
xytext=(-1,1), textcoords="offset points",
ha="center", va="baseline", fontsize=40)
annots.append(an)
ax.set_title(r'$\exp(x^2)$', y=1.05)
fig.savefig('logscale.pdf', format='pdf', bbox_inches='tight')
plt.show()