How to set the view_limits / range of an axis in Matplotlib - python-3.x

I want some space before and after the last tick location on an axis. I would just use axis.set_xlim() for example but this interferes with my (custom) locator and reruns the tick generation. I found and overwritten the view_limits() method of the locator-classes but they don't seem to be called automatically and when called manually they don't have any impact on the resulting plot. I searched the docs and the source but haven't come up with a solution. Am I missing something?
For the greater picture I want to have a locator which gives me some space before and after the ticks and chooses tick points which are a multiple of 'base' like the MultipleLocator but scale the base automatically if the number of ticks exceeds a specified value. If there is another way to achieve this without subclassing a locator I am all ears :).
Here is my example code for the subclassed locator with overwritten view_limits-method:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MaxNLocator
class MyLocator(MaxNLocator):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def view_limits(self, dmin, dmax):
bins = self.bin_boundaries(dmin, dmax)
step = bins[1] - bins[0]
result = np.array([bins[0] - step, bins[-1] + step])
print(result)
return result
a = 10.0
b = 99.0
t = np.arange(a, b, 0.1)
s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01)
loc = MyLocator(9)
fig, ax = plt.subplots()
plt.plot(t, s)
ax.xaxis.set_major_locator(loc)
loc.autoscale() # results in [ 0. 110.] but doesnt change the plot
plt.show()

Not sure, if I understood completely what your problem is, but if you only want to add extra space, you can still use MaxNLocator and add that space manually like here:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MaxNLocator
a = 10.0
b = 99.0
t = np.arange(a, b, 0.1)
s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01)
loc = MaxNLocator(9)
fig, ax = plt.subplots()
plt.plot(t, s)
ax.xaxis.set_major_locator(loc)
ticks = ax.get_xticks()
newticks = np.zeros(len(ticks)+2)
newticks[0] = ticks[0]- (ticks[1]-ticks[0])
newticks[-1] = ticks[-1]+ (ticks[1]-ticks[0])
newticks[1:-1] = ticks
ax.set_xticks(newticks)
plt.show()

One slightly hacky solution to avoid ticks close to the plot's edges is the following:
class PaddedMaxNLocator(mp.ticker.MaxNLocator):
def __init__(self, *args, protected_width=0.25, **kwargs):
# `prune` edge ticks that might now become visible
super().__init__(*args, **kwargs, prune='both')
# Clamp to some reasonable range
self.protected_width = min(0.5, protected_width)
def tick_values(self, vmin, vmax):
diff = (vmax - vmin) * self.protected_width / 2
return super().tick_values(vmin + diff, vmax - diff)

Related

Coordinate conversion problem of a FITS file

I have loaded and plotted a FITS file in python.
With the help of a previous post, I have managed to get the conversion of the axis from pixels to celestial coordinates. But I can't manage to get them in milliarcseconds (mas) correctly.
The code is the following
import numpy as np
import matplotlib.pyplot as plt
import astropy.units as u
from astropy.wcs import WCS
from astropy.io import fits
from astropy.utils.data import get_pkg_data_filename
filename = get_pkg_data_filename('hallo.fits')
hdu = fits.open(filename)[0]
wcs = WCS(hdu.header).celestial
wcs.wcs.crval = [0,0]
plt.subplot(projection=wcs)
plt.imshow(hdu.data[0][0], origin='lower')
plt.xlim(200,800)
plt.ylim(200,800)
plt.xlabel('Relative R.A ()')
plt.ylabel('Relative Dec ()')
plt.colorbar()
The output looks like
The y-label is cut for some reason, I do not know.
As it was shown in another post, one could use
wcs.wcs.ctype = [ 'XOFFSET' , 'YOFFSET' ]
to switch it to milliarcsecond, and I get
but the scale is incorrect!.
For instance, 0deg00min00.02sec should be 20 mas and not 0.000002!
Did I miss something here?
Looks like a spectral index map. Nice!
I think the issue might be that FITS implicitly uses degrees for values like CDELT. And they should be converted to mas explicitly for the plot.
The most straightforward way is to multiply CDELT values by 3.6e6 to convert from degrees to mas.
However, there is a more general approach which could be useful if you want to convert to different units at some point:
import astropy.units as u
w.wcs.cdelt = (w.wcs.cdelt * u.deg).to(u.mas)
So it basically says first that the units of CDELT are degrees and then converts them to mas.
The whole workflow is like this:
def make_transform(f):
'''use already read-in FITS file object f to build pixel-to-mas transformation'''
print("Making a transformation out of a FITS header")
w = WCS(f[0].header)
w = w.celestial
w.wcs.crval = [0, 0]
w.wcs.ctype = [ 'XOFFSET' , 'YOFFSET' ]
w.wcs.cunit = ['mas' , 'mas']
w.wcs.cdelt = (w.wcs.cdelt * u.deg).to(u.mas)
print(w.world_axis_units)
return w
def read_fits(file):
'''read fits file into object'''
try:
res = fits.open(file)
return res
except:
return None
def start_plot(i,df=None, w=None, xlim = [None, None], ylim=[None, None]):
'''starts a plot and returns fig,ax .
xlim, ylim - axes limits in mas
'''
# make a transformation
# Using a dataframe
if df is not None:
w = make_transform_df(df)
# using a header
if w is not None:
pass
# not making but using one from the arg list
else:
w = make_transform(i)
# print('In start_plot using the following transformation:\n {}'.format(w))
fig = plt.figure()
if w.naxis == 4:
ax = plt.subplot(projection = w, slices = ('x', 'y', 0 ,0 ))
elif w.naxis == 2:
ax = plt.subplot(projection = w)
# convert xlim, ylim to coordinates of BLC and TRC, perform transformation, then return back to xlim, ylim in pixels
if any(xlim) and any(ylim):
xlim_pix, ylim_pix = limits_mas2pix(xlim, ylim, w)
ax.set_xlim(xlim_pix)
ax.set_ylim(ylim_pix)
fig.add_axes(ax) # note that the axes have to be explicitly added to the figure
return fig, ax
rm = read_fits(file)
wr = make_transform(rm)
fig, ax = start_plot(RM, w=wr, xlim = xlim, ylim = ylim)
Then just plot to the axes ax with imshow or contours or whatever.
Of course, this piece of code could be reduced to meet your particular needs.

How to make the fluctuation range of three polylines all obvious in same figure by matplotlib? [duplicate]

I'm trying to create a plot using pyplot that has a discontinuous x-axis. The usual way this is drawn is that the axis will have something like this:
(values)----//----(later values)
where the // indicates that you're skipping everything between (values) and (later values).
I haven't been able to find any examples of this, so I'm wondering if it's even possible. I know you can join data over a discontinuity for, eg, financial data, but I'd like to make the jump in the axis more explicit. At the moment I'm just using subplots but I'd really like to have everything end up on the same graph in the end.
Paul's answer is a perfectly fine method of doing this.
However, if you don't want to make a custom transform, you can just use two subplots to create the same effect.
Rather than put together an example from scratch, there's an excellent example of this written by Paul Ivanov in the matplotlib examples (It's only in the current git tip, as it was only committed a few months ago. It's not on the webpage yet.).
This is just a simple modification of this example to have a discontinuous x-axis instead of the y-axis. (Which is why I'm making this post a CW)
Basically, you just do something like this:
import matplotlib.pylab as plt
import numpy as np
# If you're not familiar with np.r_, don't worry too much about this. It's just
# a series with points from 0 to 1 spaced at 0.1, and 9 to 10 with the same spacing.
x = np.r_[0:1:0.1, 9:10:0.1]
y = np.sin(x)
fig,(ax,ax2) = plt.subplots(1, 2, sharey=True)
# plot the same data on both axes
ax.plot(x, y, 'bo')
ax2.plot(x, y, 'bo')
# zoom-in / limit the view to different portions of the data
ax.set_xlim(0,1) # most of the data
ax2.set_xlim(9,10) # outliers only
# hide the spines between ax and ax2
ax.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax.yaxis.tick_left()
ax.tick_params(labeltop='off') # don't put tick labels at the top
ax2.yaxis.tick_right()
# Make the spacing between the two axes a bit smaller
plt.subplots_adjust(wspace=0.15)
plt.show()
To add the broken axis lines // effect, we can do this (again, modified from Paul Ivanov's example):
import matplotlib.pylab as plt
import numpy as np
# If you're not familiar with np.r_, don't worry too much about this. It's just
# a series with points from 0 to 1 spaced at 0.1, and 9 to 10 with the same spacing.
x = np.r_[0:1:0.1, 9:10:0.1]
y = np.sin(x)
fig,(ax,ax2) = plt.subplots(1, 2, sharey=True)
# plot the same data on both axes
ax.plot(x, y, 'bo')
ax2.plot(x, y, 'bo')
# zoom-in / limit the view to different portions of the data
ax.set_xlim(0,1) # most of the data
ax2.set_xlim(9,10) # outliers only
# hide the spines between ax and ax2
ax.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax.yaxis.tick_left()
ax.tick_params(labeltop='off') # don't put tick labels at the top
ax2.yaxis.tick_right()
# Make the spacing between the two axes a bit smaller
plt.subplots_adjust(wspace=0.15)
# This looks pretty good, and was fairly painless, but you can get that
# cut-out diagonal lines look with just a bit more work. The important
# thing to know here is that in axes coordinates, which are always
# between 0-1, spine endpoints are at these locations (0,0), (0,1),
# (1,0), and (1,1). Thus, we just need to put the diagonals in the
# appropriate corners of each of our axes, and so long as we use the
# right transform and disable clipping.
d = .015 # how big to make the diagonal lines in axes coordinates
# arguments to pass plot, just so we don't keep repeating them
kwargs = dict(transform=ax.transAxes, color='k', clip_on=False)
ax.plot((1-d,1+d),(-d,+d), **kwargs) # top-left diagonal
ax.plot((1-d,1+d),(1-d,1+d), **kwargs) # bottom-left diagonal
kwargs.update(transform=ax2.transAxes) # switch to the bottom axes
ax2.plot((-d,d),(-d,+d), **kwargs) # top-right diagonal
ax2.plot((-d,d),(1-d,1+d), **kwargs) # bottom-right diagonal
# What's cool about this is that now if we vary the distance between
# ax and ax2 via f.subplots_adjust(hspace=...) or plt.subplot_tool(),
# the diagonal lines will move accordingly, and stay right at the tips
# of the spines they are 'breaking'
plt.show()
I see many suggestions for this feature but no indication that it's been implemented. Here is a workable solution for the time-being. It applies a step-function transform to the x-axis. It's a lot of code, but it's fairly simple since most of it is boilerplate custom scale stuff. I have not added any graphics to indicate the location of the break, since that is a matter of style. Good luck finishing the job.
from matplotlib import pyplot as plt
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
import numpy as np
def CustomScaleFactory(l, u):
class CustomScale(mscale.ScaleBase):
name = 'custom'
def __init__(self, axis, **kwargs):
mscale.ScaleBase.__init__(self)
self.thresh = None #thresh
def get_transform(self):
return self.CustomTransform(self.thresh)
def set_default_locators_and_formatters(self, axis):
pass
class CustomTransform(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
lower = l
upper = u
def __init__(self, thresh):
mtransforms.Transform.__init__(self)
self.thresh = thresh
def transform(self, a):
aa = a.copy()
aa[a>self.lower] = a[a>self.lower]-(self.upper-self.lower)
aa[(a>self.lower)&(a<self.upper)] = self.lower
return aa
def inverted(self):
return CustomScale.InvertedCustomTransform(self.thresh)
class InvertedCustomTransform(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
lower = l
upper = u
def __init__(self, thresh):
mtransforms.Transform.__init__(self)
self.thresh = thresh
def transform(self, a):
aa = a.copy()
aa[a>self.lower] = a[a>self.lower]+(self.upper-self.lower)
return aa
def inverted(self):
return CustomScale.CustomTransform(self.thresh)
return CustomScale
mscale.register_scale(CustomScaleFactory(1.12, 8.88))
x = np.concatenate((np.linspace(0,1,10), np.linspace(9,10,10)))
xticks = np.concatenate((np.linspace(0,1,6), np.linspace(9,10,6)))
y = np.sin(x)
plt.plot(x, y, '.')
ax = plt.gca()
ax.set_xscale('custom')
ax.set_xticks(xticks)
plt.show()
Check the brokenaxes package:
import matplotlib.pyplot as plt
from brokenaxes import brokenaxes
import numpy as np
fig = plt.figure(figsize=(5,2))
bax = brokenaxes(
xlims=((0, .1), (.4, .7)),
ylims=((-1, .7), (.79, 1)),
hspace=.05
)
x = np.linspace(0, 1, 100)
bax.plot(x, np.sin(10 * x), label='sin')
bax.plot(x, np.cos(10 * x), label='cos')
bax.legend(loc=3)
bax.set_xlabel('time')
bax.set_ylabel('value')
A very simple hack is to
scatter plot rectangles over the axes' spines and
draw the "//" as text at that position.
Worked like a charm for me:
# FAKE BROKEN AXES
# plot a white rectangle on the x-axis-spine to "break" it
xpos = 10 # x position of the "break"
ypos = plt.gca().get_ylim()[0] # y position of the "break"
plt.scatter(xpos, ypos, color='white', marker='s', s=80, clip_on=False, zorder=100)
# draw "//" on the same place as text
plt.text(xpos, ymin-0.125, r'//', fontsize=label_size, zorder=101, horizontalalignment='center', verticalalignment='center')
Example Plot:
For those interested, I've expanded upon #Paul's answer and added it to the matplotlib wrapper proplot. It can do axis "jumps", "speedups", and "slowdowns".
There is no way currently to add "crosses" that indicate the discrete jump like in Joe's answer, but I plan to add this in the future. I also plan to add a default "tick locator" that sets sensible default tick locations depending on the CutoffScale arguments.
Adressing Frederick Nord's question how to enable parallel orientation of the diagonal "breaking" lines when using a gridspec with ratios unequal 1:1, the following changes based on the proposals of Paul Ivanov and Joe Kingtons may be helpful. Width ratio can be varied using variables n and m.
import matplotlib.pylab as plt
import numpy as np
import matplotlib.gridspec as gridspec
x = np.r_[0:1:0.1, 9:10:0.1]
y = np.sin(x)
n = 5; m = 1;
gs = gridspec.GridSpec(1,2, width_ratios = [n,m])
plt.figure(figsize=(10,8))
ax = plt.subplot(gs[0,0])
ax2 = plt.subplot(gs[0,1], sharey = ax)
plt.setp(ax2.get_yticklabels(), visible=False)
plt.subplots_adjust(wspace = 0.1)
ax.plot(x, y, 'bo')
ax2.plot(x, y, 'bo')
ax.set_xlim(0,1)
ax2.set_xlim(10,8)
# hide the spines between ax and ax2
ax.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax.yaxis.tick_left()
ax.tick_params(labeltop='off') # don't put tick labels at the top
ax2.yaxis.tick_right()
d = .015 # how big to make the diagonal lines in axes coordinates
# arguments to pass plot, just so we don't keep repeating them
kwargs = dict(transform=ax.transAxes, color='k', clip_on=False)
on = (n+m)/n; om = (n+m)/m;
ax.plot((1-d*on,1+d*on),(-d,d), **kwargs) # bottom-left diagonal
ax.plot((1-d*on,1+d*on),(1-d,1+d), **kwargs) # top-left diagonal
kwargs.update(transform=ax2.transAxes) # switch to the bottom axes
ax2.plot((-d*om,d*om),(-d,d), **kwargs) # bottom-right diagonal
ax2.plot((-d*om,d*om),(1-d,1+d), **kwargs) # top-right diagonal
plt.show()
This is a hacky but pretty solution for x-axis breaks.
The solution is based on https://matplotlib.org/stable/gallery/subplots_axes_and_figures/broken_axis.html, which gets rid of the problem with positioning the break above the spine, solved by How can I plot points so they appear over top of the spines with matplotlib?
from matplotlib.patches import Rectangle
import matplotlib.pyplot as plt
def axis_break(axis, xpos=[0.1, 0.125], slant=1.5):
d = slant # proportion of vertical to horizontal extent of the slanted line
anchor = (xpos[0], -1)
w = xpos[1] - xpos[0]
h = 1
kwargs = dict(marker=[(-1, -d), (1, d)], markersize=12, zorder=3,
linestyle="none", color='k', mec='k', mew=1, clip_on=False)
axis.add_patch(Rectangle(
anchor, w, h, fill=True, color="white",
transform=axis.transAxes, clip_on=False, zorder=3)
)
axis.plot(xpos, [0, 0], transform=axis.transAxes, **kwargs)
fig, ax = plt.subplots(1,1)
plt.plot(np.arange(10))
axis_break(ax, xpos=[0.1, 0.12], slant=1.5)
axis_break(ax, xpos=[0.3, 0.31], slant=-10)
if you want to replace an axis label, this would do the trick:
from matplotlib import ticker
def replace_pos_with_label(fig, pos, label, axis):
fig.canvas.draw() # this is needed to set up the x-ticks
labs = axis.get_xticklabels()
labels = []
locs = []
for text in labs:
x = text._x
lab = text._text
if x == pos:
lab = label
labels.append(lab)
locs.append(x)
axis.xaxis.set_major_locator(ticker.FixedLocator(locs))
axis.set_xticklabels(labels)
fig, ax = plt.subplots(1,1)
plt.plot(np.arange(10))
replace_pos_with_label(fig, 0, "-10", axis=ax)
replace_pos_with_label(fig, 6, "$10^{4}$", axis=ax)
axis_break(ax, xpos=[0.1, 0.12], slant=2)

How to loop and plot correctly on 4D Nifti MRI image

I have 4D NIFTI images with different dimensions [x,y,slices,frames], the first two are the spatial resolution, the third is slice number, while the last one is frame number, I tried to plot all the slices of a specific frame into one figure and update frame by frame using for loops instead of doing all the indexing manually as before, but I have a problem that my images are not updating the frame (except the last one down) as you can see in the attached photo, how can I solve this issue please ??
#==================================
import nibabel as nib
import numpy as np
import matplotlib.pyplot as plt
#==================================
# load image (4D) [X,Y,Z_slice,time]
nii_img = nib.load(path)
nii_data = nii_img.get_fdata()
#===================================================
fig, ax = plt.subplots(4,3,constrained_layout=True)
fig.canvas.set_window_title('4D Nifti Image')
fig.suptitle('4D_Nifti 10 slices 30 time Frames', fontsize=16)
#-------------------------------------------------------------------------------
mng = plt.get_current_fig_manager()
mng.full_screen_toggle()
slice_counter = 0
for i in range(30):
for j in range(3):
for k in range(3):
if slice_counter<9:
ax[j,k].cla()
ax[j,k].imshow(nii_data[:,:,slice_counter,i],cmap='gray', interpolation=None)
ax[j,k].set_title("frame {}".format(i))
ax[j,k].axis('off')
slice_counter+=1
else:
#---------------------------------
ax[3,0].axis('off')
ax[3,2].axis('off')
#---------------------------------
ax[3,1].cla()
ax[3,1].nii_data(nii_data[:,:,9,i],cmap='gray', interpolation=None)
ax[3,1].set_title("frame {}".format(i))
ax[3,1].axis('off')
#---------------------------------
# Note that using time.sleep does *not* work here!
#---------------------------------
plt.pause(.05)
plt.close('all')
At the moment it is not quite clear to me how your output should look like because the second column in the image has more entries than the others.
Please clarify this better in your questions as well as updating your code which is not working due to inconsistent variable names and messed up indenting.
In the meanwhile, I will try it with a first shot where your goal is to print all your slices on the x-axis whereas each frame is on the y-axis.
The code I adapted that it will print for the first three slices the first four frames.
#==================================
import nibabel as nib
import numpy as np
import matplotlib.pyplot as plt
#==================================
# load image (4D) [X,Y,Z_slice,time]
nii_img = nib.load(path)
nii_data = nii_img.get_fdata()
#===================================================
number_of_slices = 3
number_of_frames = 4
fig, ax = plt.subplots(number_of_frames, number_of_slices,constrained_layout=True)
fig.canvas.set_window_title('4D Nifti Image')
fig.suptitle('4D_Nifti 10 slices 30 time Frames', fontsize=16)
#-------------------------------------------------------------------------------
mng = plt.get_current_fig_manager()
mng.full_screen_toggle()
for slice in range(number_of_slices):
for frame in range(number_of_frames):
ax[frame, slice].imshow(nii_data[:,:,slice,frame],cmap='gray', interpolation=None)
ax[frame, slice].set_title("layer {} / frame {}".format(slice, frame))
ax[frame, slice].axis('off')
plt.show()
The sample output for a black image looks like this:
sample output
Update - 05.04.2020
Given the information from the discussion in the comments here the updated version:
#==================================
import nibabel as nib
import numpy as np
import matplotlib.pyplot as plt
from math import ceil
#==================================
# Load image (4D) [X,Y,Z_slice,time]
nii_img = nib.load(path)
nii_data = nii_img.get_fdata()
#===================================================
number_of_slices = nii_data.shape[2]
number_of_frames = nii_data.shape[3]
# Define subplot layout
aspect_ratio = 16./9
number_of_colums = int(number_of_slices / aspect_ratio)
if( number_of_slices % number_of_colums > 0):
number_of_colums += 1
number_of_rows = ceil(number_of_slices / number_of_colums)
# Setup figure
fig, axs = plt.subplots(number_of_rows, number_of_colums,constrained_layout=True)
fig.canvas.set_window_title('4D Nifti Image')
fig.suptitle('4D_Nifti {} slices {} time Frames'.format(number_of_slices, number_of_frames), fontsize=16)
#-------------------------------------------------------------------------------
mng = plt.get_current_fig_manager()
mng.full_screen_toggle()
for frame in range(number_of_frames):
for slice, ax in enumerate(axs.flat):
# For every slice print the image otherwise show empty space.
if slice < number_of_slices:
ax.imshow(nii_data[:,:,slice,frame],cmap='gray', interpolation=None)
ax.set_title("layer {} / frame {}".format(slice, frame))
ax.axis('off')
else:
ax.axis('off')
plt.pause(0.05)
plt.close('all')
The output will look like: second sample output

Log scales with Seaborn kdeplot

I am trying to make a nice free energy surface (heat map) using Seaborn's kdeplot.
I am very close but can not figure out a way to change the color bar scale. The color bar scale is important since it is supposed to represent the difference in energy at different coordinates on the map. I need to know how to scale the values of the color bar by -(0.5961573)*log(x), where x is the values of the color bar. I may also then need to normalize the color bar from there so that the max value is 0.
Here is what I currently have:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
import seaborn as sns
rs=[]
dihes=[]
with open(sys.argv[1], 'r') as f:
for line in f:
time,r,dihe = line.split()
rs.append(float(r))
dihes.append(float(dihe))
sns.set_style("white")
sns.kdeplot(rs, dihes, n_levels=25, cbar=True, cmap="Purples_d")
plt.show()
This gets me:
The arrays rs and dihes are simple one dimensional arrays.
Any suggestions on how to scale the color bar (z-axis) would be very helpful!
One way to do it is to create the graph manually and then modify the labels directly. This involves a couple more lines of code. You may have to tweak the formatting a bit but something like this should get you on the right track.
The following is adapted from this answer and this answer.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
rs=[]
dihes=[]
with open(sys.argv[1], 'r') as f:
for line in f:
time,r,dihe = line.split()
rs.append(float(r))
dihes.append(float(dihe))
x = rs
y = dihes
kde = stats.gaussian_kde([x, y])
xx, yy = np.mgrid[min(x):max(x):(max(x)-min(x))/100, min(y):max(y):(max(y)-min(y))/100]
density = kde(np.c_[xx.flat, yy.flat].T).reshape(xx.shape)
sns.set_style("white")
fig, ax = plt.subplots()
cset = ax.contour(xx, yy, density, 25, cmap="Purples_r")
cb = fig.colorbar(cset)
cb.ax.set_yticklabels(map(lambda x: -0.5961573*np.log(float(x.get_text())),
cb.ax.get_yticklabels()))
A bit late to the party, but I ended up putting together this context manager which switches plotted density values to a logarithmic scale:
import contextlib
import seaborn as sns
#contextlib.contextmanager
def plot_kde_as_log(base=np.exp(1), support_threshold=1e-4):
"""Context manager to render density estimates on a logarithmic scale.
Usage:
with plot_kde_as_log():
sns.jointplot(x='x', y='y', data=df, kind='kde')
"""
old_stats = sns.distributions._has_statsmodels
old_univar = sns.distributions._scipy_univariate_kde
old_bivar = sns.distributions._scipy_bivariate_kde
sns.distributions._has_statsmodels = False
def log_clip_fn(v):
v = np.log(np.clip(v, support_threshold, np.inf))
v -= np.log(support_threshold)
v /= np.log(base)
return v
def new_univar(*args, **kwargs):
x, y = old_univar(*args, **kwargs)
y = log_clip_fn(y)
return x, y
def new_bivar(*args, **kwargs):
x, y, z = old_bivar(*args, **kwargs)
z = log_clip_fn(z)
return x, y, z
sns.distributions._scipy_univariate_kde = new_univar
sns.distributions._scipy_bivariate_kde = new_bivar
try:
yield
finally:
sns.distributions._has_statsmodels = old_stats
sns.distributions._scipy_univariate_kde = old_univar
sns.distributions._scipy_bivariate_kde = old_bivar
The benefit of this approach is that it keeps all of the styling and other options of sns.jointplot() without any additional effort.
I updated Walt W's context manager to work with newer versions of seaborn
#contextlib.contextmanager
def plot_kde_as_log(base=np.exp(1), support_threshold=1e-4):
"""Context manager to render density estimates on a logarithmic scale.
Usage:
with plot_kde_as_log():
sns.jointplot(x='x', y='y', data=df, kind='kde')
"""
old_call = sns._statistics.KDE.__call__
def log_clip_fn(v):
v = np.log(np.clip(v, support_threshold, np.inf))
v -= np.log(support_threshold)
v /= np.log(base)
return v
def new_call(*args, **kwargs):
density, support = old_call(*args, **kwargs)
density = log_clip_fn(density)
return density, support
sns._statistics.KDE.__call__ = new_call
try:
yield
finally:
sns._statistics.KDE.__call__ = old_call

Python: Matplotlib: update graph by time in second

I have a series to plot at y-axis.
y = [3,4,5,1,4,7,4,7,1,9]
However, I want to plot it by recent time by second. I've done it like this,
import time
def xtime():
t = time.strftime("%H%M%S")
t = int(t)
xtime = [t]
while xtime:
t = time.strftime("%H%M%S")
t = int(t)
xtime.extend([t])
time.sleep(1)
I'm having problem when I want to plot each one of the number at y by each second. Please correct my code here,
import time
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
def animate(i):
x = xtime()
y = [3,4,5,1,4,7,4,7,1,9]
plt.plot(x,y)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
xtime function is referred as code at first.
Thanks!
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
# Y data
ydata = [3,4,5,1,4,7,4,7,1,9]
# how many points
N = len(ydata)
# make x data
xdata = np.arange(N)
def animate(i):
# update the date in our Line2D artist
# note that when run this will look at the global namespace for
# an object called `ln` which we will define later
ln.set_data(xdata[:i], ydata[:i])
# return the updated artist for the blitting
return ln,
# make our figure and axes
fig, ax = plt.subplots()
# make the artist we will be using. Note this was used in `animate`
ln, = ax.plot([], [], animated=True)
# set the axes limits
ax.set_xlim(0, N)
ax.set_ylim(0, 10)
# run the animation. Keeping a ref to the animation object is important
# as if it gets garbage collected it takes you timer and callbacks with it
ani = animation.FuncAnimation(fig, animate, frames=N, interval=1000, blit=True)

Resources