matplotlib LogFormatterExponent -- 'e' in the exponent labels of cbar - python

So I'm modifying someone else's library to setup a cbar with log (values). I thought I could use LogFormatterExponent() ... But it seemingly randomly adds and 'e' to the exponents that it uses for the cbar. What's going on? How can I suppress/fix this?
if show_cbar:
if log:
l_f = LogFormatterExponent()
else:
l_f = ScalarFormatter()
if qtytitle is not None:
plt.colorbar(ims,format=l_f).set_label(qtytitle)
else:
plt.colorbar(ims,format=l_f).set_label(units)
Here's what I'm seeing for log=True:
And another plot where log = False:
At first, I thought the 'e's were being cut-off by the label at right... but over several plots this doesn't appear to be the case. I usually get 1-2 'e's ... But on a plot with only 3 color bar ticks, I see none!

A minimal example is
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as cm
import matplotlib.ticker as ct
data = np.exp(np.random.rand(20, 20) * 100)
fig, ax = plt.subplots()
log_norm = cm.LogNorm()
im = ax.imshow(data, interpolation='nearest', cmap='viridis', norm=log_norm)
fig.colorbar(im, format=ct.LogFormatterExponent())
This looks like a bug in mpl. If you already have a large library, I would just include a fixed version of the formatter.
class LogFormatterExponentFixed(LogFormatter):
"""
Format values for log axis; using ``exponent = log_base(value)``
"""
def __call__(self, x, pos=None):
"""Return the format for tick val *x* at position *pos*"""
vmin, vmax = self.axis.get_view_interval()
vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
d = abs(vmax - vmin)
b = self._base
if x == 0:
return '0'
sign = np.sign(x)
# only label the decades
fx = math.log(abs(x)) / math.log(b)
isDecade = is_close_to_int(fx)
if not isDecade and self.labelOnlyBase:
s = ''
elif abs(fx) > 10000:
s = '%1.0g' % fx
elif abs(fx) < 1:
s = '%1.0g' % fx
else:
# this is the added line
fd = math.log(abs(d)) / math.log(b)
s = self.pprint_val(fx, fd)
if sign == -1:
s = '-%s' % s
return self.fix_minus(s)
Working on a fix for upstream.

Related

Button press event - event handling and picking: IndexError: list index out of range

I have the following command below.
The overall aim is to allow the user to select a Y-value by pressing on the bar graph. The colour of each bar should then change depending on what this Y-value is.
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import matplotlib.colors
np.random.seed(12345)
df = pd.DataFrame({"values":[np.random.normal(32000,200000,3650).mean(),
np.random.normal(43000,100000,3650).mean(),
np.random.normal(43500,140000,3650).mean(),
np.random.normal(48000,70000,3650).mean()],
"index":[1992,1993,1994,1995]})
standarderrors1992 = stats.sem(np.random.normal(32000,200000,3650))
standarderrors1993 = stats.sem(np.random.normal(43000,100000,3650))
standarderrors1994 = stats.sem(np.random.normal(43500,140000,3650))
standarderrors1995 = stats.sem(np.random.normal(48000,70000,3650))
add1992 = 1.96*standarderrors1992
add1993 = 1.96*standarderrors1993
add1994 = 1.96*standarderrors1994
add1995 = 1.96*standarderrors1995
mean1992 = np.random.normal(32000,200000,3650).mean()
mean1993 = np.random.normal(43000,100000,3650).mean()
mean1994 = np.random.normal(43500,140000,3650).mean()
mean1995 = np.random.normal(48000,70000,3650).mean()
labels = [1992,1993,1994,1995]
add = [add1992,add1992,add1994,add1995]
This first part organises the raw data.
def onclick(event):
plt.cla()
plt.bar(df["index"].values,df["values"].values,align='center', alpha=0.5,yerr=add)
plt.xticks(labels)
plt.show()
limit = event.ydata
limits = []
limits.append(limit)
if len(limits) >= 1:
plt.gcf().canvas.mpl_disconnect(plt.gcf().canvas.mpl_connect('button_press_event', onclick))
dict = {mean1992:add1992,mean1993:add1993,mean1994:add1994,mean1995:add1995}
colourofbars = []
for key,value in dict.items():
if limits[0] > (key+(value)):
colour = 1
colourofbars.append(colour)
elif limits[0] < (key-(value)):
colour = 0
colourofbars.append(colour)
elif (limits[0] < (key+(value))) and (limits[0] > (key-(value))):
colour = ((key+(value))-limits[0])/((key+value)-(key-value))
colourofbars.append(colour)
df["colourofbars"] = colourofbars
cmap = plt.cm.rainbow
norm = matplotlib.colors.Normalize(vmin=min(df["colourofbars"].values),vmax=max(df["colourofbars"].values))
plt.bar(df["index"].values,df["values"].values,color=cmap(norm(df["colourofbars"].values)),align='center', alpha=0.5,yerr=add)
plt.xticks(labels)
plt.axhline(y=limits[0],linewidth=1, color='k')
plt.title('Graph showing proportion of confidence interval lying above the threshold value',y=1.05)
plt.ylabel('Possible Y-threshold values',labelpad=5)
plt.xlabel('Year',labelpad=5)
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
plt.gcf().colorbar(sm)
plt.gcf().canvas.mpl_connect('button_press_event', onclick)
Here, the user presses on the graph to select a value, which is then assigned to the list 'limits'. The values in the list 'colourofbars' are appended accordingly.
Next, a different colour is assigned to each bar in the bar chart depending on the values in the column 'colourofbars', after converting the list to a column in the dataframe 'df'. I then try to plot a legend showing this colour gradient scale.
However, I keep getting the error: IndexError: list index out of range. Could anyone give me a helping hand as to where I am going wrong?
I have modified the function onclick() by removing these lines and placing them outside the function:
plt.cla()
plt.bar(df["index"].values,df["values"].values,align='center', alpha=0.5,yerr=add)
plt.xticks(labels)
plt.show()
Thus, the correct code looks like this:
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import matplotlib.colors
np.random.seed(12345)
df = pd.DataFrame({"values":[np.random.normal(32000,200000,3650).mean(),
np.random.normal(43000,100000,3650).mean(),
np.random.normal(43500,140000,3650).mean(),
np.random.normal(48000,70000,3650).mean()],
"index":[1992,1993,1994,1995]})
standarderrors1992 = stats.sem(np.random.normal(32000,200000,3650))
standarderrors1993 = stats.sem(np.random.normal(43000,100000,3650))
standarderrors1994 = stats.sem(np.random.normal(43500,140000,3650))
standarderrors1995 = stats.sem(np.random.normal(48000,70000,3650))
add1992 = 1.96*standarderrors1992
add1993 = 1.96*standarderrors1993
add1994 = 1.96*standarderrors1994
add1995 = 1.96*standarderrors1995
mean1992 = np.random.normal(32000,200000,3650).mean()
mean1993 = np.random.normal(43000,100000,3650).mean()
mean1994 = np.random.normal(43500,140000,3650).mean()
mean1995 = np.random.normal(48000,70000,3650).mean()
labels = [1992,1993,1994,1995]
add = [add1992,add1992,add1994,add1995]
plt.cla()
plt.bar(df["index"].values,df["values"].values,align='center', alpha=0.5,yerr=add)
plt.xticks(labels)
plt.show()
def onclick(event):
limit = event.ydata
limits = []
limits.append(limit)
if len(limits) >= 1:
plt.gcf().canvas.mpl_disconnect(plt.gcf().canvas.mpl_connect('button_press_event', onclick))
dict = {mean1992:add1992,mean1993:add1993,mean1994:add1994,mean1995:add1995}
colourofbars = []
for key,value in dict.items():
if limits[0] > (key+(value)):
colour = 1
colourofbars.append(colour)
elif limits[0] < (key-(value)):
colour = 0
colourofbars.append(colour)
elif (limits[0] < (key+(value))) and (limits[0] > (key-(value))):
colour = ((key+(value))-limits[0])/((key+value)-(key-value))
colourofbars.append(colour)
df["colourofbars"] = colourofbars
cmap = plt.cm.rainbow
norm = matplotlib.colors.Normalize(vmin=min(df["colourofbars"].values),vmax=max(df["colourofbars"].values))
plt.bar(df["index"].values,df["values"].values,color=cmap(norm(df["colourofbars"].values)),align='center', alpha=0.5,yerr=add)
plt.xticks(labels)
plt.axhline(y=limits[0],linewidth=1, color='k')
plt.title('Graph showing proportion of confidence interval lying above the threshold value',pad=5)
plt.ylabel('Possible Y-threshold values',labelpad=5)
plt.xlabel('Year',labelpad=5)
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
plt.gcf().colorbar(sm)
plt.gcf().canvas.mpl_connect('button_press_event', onclick)
I am no longer getting the above error message. When I run this code, this figure appears:
When I then click on this graph, the correct output appears:

Plot dashed line interrupted with data (similar to contour plot)

I am stuck with a (hopefully) simple problem.
My aim is to plot a dashed line interrupted with data (not only text).
As I only found out to create a dashed line via linestyle = 'dashed', any help is appreciated to put the data between the dashes.
Something similar, regarding the labeling, is already existing with Matplotlib - as I saw in the contour line demo.
Update:
The question link mentioned by Richard in comments was very helpful, but not the 100% like I mentioned via comment.
Currently, I do it this way:
line_string2 = '-10 ' + u"\u00b0" +"C"
l, = ax1.plot(T_m10_X_Values,T_m10_Y_Values)
pos = [(T_m10_X_Values[-2]+T_m10_X_Values[-1])/2., (T_m10_Y_Values[-2]+T_m10_Y_Values[-1])/2.]
# transform data points to screen space
xscreen = ax1.transData.transform(zip(T_m10_Y_Values[-2::],T_m10_Y_Values[-2::]))
rot = np.rad2deg(np.arctan2(*np.abs(np.gradient(xscreen)[0][0][::-1])))
ltex = plt.text(pos[0], pos[1], line_string2, size=9, rotation=rot, color='b',ha="center", va="bottom",bbox = dict(ec='1',fc='1', alpha=0.5))
Here you can see a snapshot of the result. The minus 20°C is without BBox.
Quick and dirty answer using annotate:
import matplotlib.pyplot as plt
import numpy as np
x = list(reversed([1.81,1.715,1.78,1.613,1.629,1.714,1.62,1.738,1.495,1.669,1.57,1.877,1.385]))
y = [0.924,0.915,0.914,0.91,0.909,0.905,0.905,0.893,0.886,0.881,0.873,0.873,0.844]
def plot_with_text(x, y, text, text_count=None):
text_count = (2 * (len(x) / len(text))) if text_count is None else text_count
fig, ax = plt.subplots(1,1)
l, = ax.plot(x,y)
text_size = len(text) * 10
idx_step = len(x) / text_count
for idx_num in range(text_count):
idx = int(idx_num * idx_step)
text_pos = [x[idx], y[idx]]
xscreen = ax.transData.transform(zip(x[max(0, idx-1):min(len(x), idx+2)], y[max(0, idx-1):min(len(y), idx+2)]))
a = np.abs(np.gradient(xscreen)[0][0])
rot = np.rad2deg(np.arctan2(*a)) - 90
ax.annotate(text, xy=text_pos, color="r", bbox=dict(ec="1", fc="1", alpha=0.9), rotation=rot, ha="center", va="center")
plot_with_text(x, y, "test")
Yields:
You can play with the offsets for more pleasing results.

No exponential form of the z-axis in matplotlib-3D-plots

I have a similar problem as described in How to prevent numbers being changed to exponential form in Python matplotlib figure:
I don't want that (in my special case) weird scientific formatting of the axis. My problem is different as I have this problem at my z-Axis. For 2-D plots I can use ax.get_yaxis().get_major_formatter().set_useOffset(False). And there is no function ax.get_zaxis()
What do I use to format my z-Axis the same way?
EDIT: Example:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import sys
import matplotlib
import matplotlib.pyplot as pyplot
def func(xi, ti):
res = 10e3 + np.cos(ti) * np.sin(xi)
return res
if __name__ == '__main__':
timeSpacing = 20
timeStart = 0
timeEnd = 1
time = np.linspace(timeStart, timeEnd, timeSpacing)
widthSpacing = 50
widthStart = 0
widthEnd = 3
width = np.linspace(widthStart, widthEnd, widthSpacing)
resList = []
matplotlib.rcParams['legend.fontsize'] = 10
fig = pyplot.figure()
ax = fig.gca(projection = '3d')
for i, item in enumerate(time):
ti = [item for t in width]
res = func(width, ti)
ax.plot(width, ti, res, 'b')
ax.set_xlabel('x')
ax.set_ylabel('t')
ax.set_zlabel('f(x,t)')
pyplot.show()
As you say, there is no get_zaxis() method. But, fortunately, there is zaxis field (so don't add ()). There are also xaxis and yaxis fields, so you can use all of those uniformly instead of get_...axis() if you like.
For example:
if __name__ == '__main__':
...
ax = fig.gca(projection = '3d')
ax.zaxis.get_major_formatter().set_useOffset(False) # here
for i, item in enumerate(time):
...
and the end result should look something like this:
As you can see, for large numbers it might not look so well...

finding local maximum from fft of a signal

I'm trying to find a peak of an fft of a signal to be used for a further analysis of the signal. I'm using a SpanSelect of data and doing an fft, represented as a frequency spectrum. I really wanted to have the plot be interactive and the user click a point to be further analyzed, but I don't see a way to do that so would like a way to find local frequency peaks. The frequency spectrum may look like this:
So I would want a way to return the frequency that has a peak at 38 hz for example. Is there a way to do this?
use argrelextrema for finding local maxima:
import numpy as np
from scipy.signal import argrelextrema
from matplotlib.pyplot import *
np.random.seed()
x = np.random.random(50)
m = argrelextrema(x, np.greater) #array of indexes of the locals maxima
y = [x[m] for i in m]
plot(x)
plot(m, y, 'rs')
show()
You can do something like that using matplotlib widgets, for example check out the lasso method of selecting points.
You can then use the selected point in any form of analysis you need.
EDIT: Combined lasso and SpanSelect widget from matplotlib examples
#!/usr/bin/env python
from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector, LassoSelector
from matplotlib.path import Path
import matplotlib.pyplot as plt
try:
raw_input
except NameError:
# Python 3
raw_input = input
class SelectFromCollection(object):
"""Select indices from a matplotlib collection using `LassoSelector`.
Selected indices are saved in the `ind` attribute. This tool highlights
selected points by fading them out (i.e., reducing their alpha values).
If your collection has alpha < 1, this tool will permanently alter them.
Note that this tool selects collection objects based on their *origins*
(i.e., `offsets`).
Parameters
----------
ax : :class:`~matplotlib.axes.Axes`
Axes to interact with.
collection : :class:`matplotlib.collections.Collection` subclass
Collection you want to select from.
alpha_other : 0 <= float <= 1
To highlight a selection, this tool sets all selected points to an
alpha value of 1 and non-selected points to `alpha_other`.
"""
def __init__(self, ax, collection, alpha_other=0.3):
self.canvas = ax.figure.canvas
self.collection = collection
self.alpha_other = alpha_other
self.xys = collection.get_offsets()
self.Npts = len(self.xys)
# Ensure that we have separate colors for each object
self.fc = collection.get_facecolors()
if len(self.fc) == 0:
raise ValueError('Collection must have a facecolor')
elif len(self.fc) == 1:
self.fc = np.tile(self.fc, self.Npts).reshape(self.Npts, -1)
self.lasso = LassoSelector(ax, onselect=self.onselect)
self.ind = []
def onselect(self, verts):
path = Path(verts)
self.ind = np.nonzero([path.contains_point(xy) for xy in self.xys])[0]
self.fc[:, -1] = self.alpha_other
self.fc[self.ind, -1] = 1
self.collection.set_facecolors(self.fc)
self.canvas.draw_idle()
def disconnect(self):
self.lasso.disconnect_events()
self.fc[:, -1] = 1
self.collection.set_facecolors(self.fc)
self.canvas.draw_idle()
def onselect(xmin, xmax):
indmin, indmax = np.searchsorted(x, (xmin, xmax))
indmax = min(len(x)-1, indmax)
thisx = x[indmin:indmax]
thisy = y[indmin:indmax]
line2.set_data(thisx, thisy)
ax2.set_xlim(thisx[0], thisx[-1])
ax2.set_ylim(thisy.min(), thisy.max())
fig.canvas.draw()
if __name__ == '__main__':
plt.ion()
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(211, axisbg='#FFFFCC')
x = np.arange(0.0, 5.0, 0.01)
y = np.sin(2*np.pi*x) + 0.5*np.random.randn(len(x))
ax.plot(x, y, '-')
ax.set_ylim(-2,2)
ax.set_title('Press left mouse button and drag to test')
ax2 = fig.add_subplot(212, axisbg='#FFFFCC')
line2, = ax2.plot(x, y, '-')
pts = ax2.scatter(x, y)
# set useblit True on gtkagg for enhanced performance
span = SpanSelector(ax, onselect, 'horizontal', useblit=True,
rectprops=dict(alpha=0.5, facecolor='red') )
selector = SelectFromCollection(ax2, pts)
plt.draw()
raw_input('Press any key to accept selected points')
print("Selected points:")
print(selector.xys[selector.ind])
selector.disconnect()
# Block end of script so you can check that the lasso is disconnected.
raw_input('Press any key to quit')

Print string over plotted line (mimic contour plot labels)

The contour plot demo shows how you can plot the curves with the level value plotted over them, see below.
Is there a way to do this same thing for a simple line plot like the one obtained with the code below?
import matplotlib.pyplot as plt
x = [1.81,1.715,1.78,1.613,1.629,1.714,1.62,1.738,1.495,1.669,1.57,1.877,1.385]
y = [0.924,0.915,0.914,0.91,0.909,0.905,0.905,0.893,0.886,0.881,0.873,0.873,0.844]
# This is the string that should show somewhere over the plotted line.
line_string = 'name of line'
# plotting
plt.plot(x,y)
plt.show()
You could simply add some text (MPL Gallery) like
import matplotlib.pyplot as plt
import numpy as np
x = [1.81,1.715,1.78,1.613,1.629,1.714,1.62,1.738,1.495,1.669,1.57,1.877,1.385]
y = [0.924,0.915,0.914,0.91,0.909,0.905,0.905,0.893,0.886,0.881,0.873,0.873,0.844]
# This is the string that should show somewhere over the plotted line.
line_string = 'name of line'
# plotting
fig, ax = plt.subplots(1,1)
l, = ax.plot(x,y)
pos = [(x[-2]+x[-1])/2., (y[-2]+y[-1])/2.]
# transform data points to screen space
xscreen = ax.transData.transform(zip(x[-2::],y[-2::]))
rot = np.rad2deg(np.arctan2(*np.abs(np.gradient(xscreen)[0][0][::-1])))
ltex = plt.text(pos[0], pos[1], line_string, size=9, rotation=rot, color = l.get_color(),
ha="center", va="center",bbox = dict(ec='1',fc='1'))
def updaterot(event):
"""Event to update the rotation of the labels"""
xs = ax.transData.transform(zip(x[-2::],y[-2::]))
rot = np.rad2deg(np.arctan2(*np.abs(np.gradient(xs)[0][0][::-1])))
ltex.set_rotation(rot)
fig.canvas.mpl_connect('button_release_event', updaterot)
plt.show()
which gives
This way you have maximum control.
Note, the rotation is in degrees and in screen not data space.
Update:
As I recently needed automatic label rotations which update on zooming and panning, thus I updated my answer to account for these needs. Now the label rotation is updated on every mouse button release (the draw_event alone was not triggered when zooming). This approach uses matplotlib transformations to link the data and screen space as discussed in this tutorial.
Based on Jakob's code, here is a function that rotates the text in data space, puts labels near a given x or y data coordinate, and works also with log plots.
def label_line(line, label_text, near_i=None, near_x=None, near_y=None, rotation_offset=0, offset=(0,0)):
"""call
l, = plt.loglog(x, y)
label_line(l, "text", near_x=0.32)
"""
def put_label(i):
"""put label at given index"""
i = min(i, len(x)-2)
dx = sx[i+1] - sx[i]
dy = sy[i+1] - sy[i]
rotation = np.rad2deg(math.atan2(dy, dx)) + rotation_offset
pos = [(x[i] + x[i+1])/2. + offset[0], (y[i] + y[i+1])/2 + offset[1]]
plt.text(pos[0], pos[1], label_text, size=9, rotation=rotation, color = line.get_color(),
ha="center", va="center", bbox = dict(ec='1',fc='1'))
x = line.get_xdata()
y = line.get_ydata()
ax = line.get_axes()
if ax.get_xscale() == 'log':
sx = np.log10(x) # screen space
else:
sx = x
if ax.get_yscale() == 'log':
sy = np.log10(y)
else:
sy = y
# find index
if near_i is not None:
i = near_i
if i < 0: # sanitize negative i
i = len(x) + i
put_label(i)
elif near_x is not None:
for i in range(len(x)-2):
if (x[i] < near_x and x[i+1] >= near_x) or (x[i+1] < near_x and x[i] >= near_x):
put_label(i)
elif near_y is not None:
for i in range(len(y)-2):
if (y[i] < near_y and y[i+1] >= near_y) or (y[i+1] < near_y and y[i] >= near_y):
put_label(i)
else:
raise ValueError("Need one of near_i, near_x, near_y")

Categories