I am trying to set the format to two decimal numbers in a matplotlib subplot environment. Unfortunately, I do not have any idea how to solve this task.
To prevent using scientific notation on the y-axis I used ScalarFormatter(useOffset=False) as you can see in my snippet below. I think my task should be solved by passing further options/arguments to the used formatter. However, I could not find any hint in matplotlib's documentation.
How can I set two decimal digits or none (both cases are needed)? I am not able to provide sample data, unfortunately.
-- SNIPPET --
f, axarr = plt.subplots(3, sharex=True)
data = conv_air
x = range(0, len(data))
axarr[0].scatter(x, data)
axarr[0].set_ylabel('$T_\mathrm{air,2,2}$', size=FONT_SIZE)
axarr[0].yaxis.set_major_locator(MaxNLocator(5))
axarr[0].yaxis.set_major_formatter(ScalarFormatter(useOffset=False))
axarr[0].tick_params(direction='out', labelsize=FONT_SIZE)
axarr[0].grid(which='major', alpha=0.5)
axarr[0].grid(which='minor', alpha=0.2)
data = conv_dryer
x = range(0, len(data))
axarr[1].scatter(x, data)
axarr[1].set_ylabel('$T_\mathrm{dryer,2,2}$', size=FONT_SIZE)
axarr[1].yaxis.set_major_locator(MaxNLocator(5))
axarr[1].yaxis.set_major_formatter(ScalarFormatter(useOffset=False))
axarr[1].tick_params(direction='out', labelsize=FONT_SIZE)
axarr[1].grid(which='major', alpha=0.5)
axarr[1].grid(which='minor', alpha=0.2)
data = conv_lambda
x = range(0, len(data))
axarr[2].scatter(x, data)
axarr[2].set_xlabel('Iterationsschritte', size=FONT_SIZE)
axarr[2].xaxis.set_major_locator(MaxNLocator(integer=True))
axarr[2].set_ylabel('$\lambda$', size=FONT_SIZE)
axarr[2].yaxis.set_major_formatter(ScalarFormatter(useOffset=False))
axarr[2].yaxis.set_major_locator(MaxNLocator(5))
axarr[2].tick_params(direction='out', labelsize=FONT_SIZE)
axarr[2].grid(which='major', alpha=0.5)
axarr[2].grid(which='minor', alpha=0.2)
See the relevant documentation in general and specifically
from matplotlib.ticker import FormatStrFormatter
fig, ax = plt.subplots()
ax.yaxis.set_major_formatter(FormatStrFormatter('%.2f'))
If you are directly working with matplotlib's pyplot (plt) and if you are more familiar with the new-style format string, you can try this:
from matplotlib.ticker import StrMethodFormatter
plt.gca().yaxis.set_major_formatter(StrMethodFormatter('{x:,.0f}')) # No decimal places
plt.gca().yaxis.set_major_formatter(StrMethodFormatter('{x:,.2f}')) # 2 decimal places
From the documentation:
class matplotlib.ticker.StrMethodFormatter(fmt)
Use a new-style format string (as used by str.format()) to format the
tick.
The field used for the value must be labeled x and the field used for
the position must be labeled pos.
The answer above is probably the correct way to do it, but didn't work for me.
The hacky way that solved it for me was the following:
ax = <whatever your plot is>
# get the current labels
labels = [item.get_text() for item in ax.get_xticklabels()]
# Beat them into submission and set them back again
ax.set_xticklabels([str(round(float(label), 2)) for label in labels])
# Show the plot, and go home to family
plt.show()
format labels using lambda function
3x the same plot with differnt y-labeling
Minimal example
import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt
from matplotlib.ticker import FormatStrFormatter
fig, axs = mpl.pylab.subplots(1, 3)
xs = np.arange(10)
ys = 1 + xs ** 2 * 1e-3
axs[0].set_title('default y-labeling')
axs[0].scatter(xs, ys)
axs[1].set_title('custom y-labeling')
axs[1].scatter(xs, ys)
axs[2].set_title('x, pos arguments')
axs[2].scatter(xs, ys)
fmt = lambda x, pos: '1+ {:.0f}e-3'.format((x-1)*1e3, pos)
axs[1].yaxis.set_major_formatter(mpl.ticker.FuncFormatter(fmt))
fmt = lambda x, pos: 'x={:f}\npos={:f}'.format(x, pos)
axs[2].yaxis.set_major_formatter(mpl.ticker.FuncFormatter(fmt))
You can also use 'real'-functions instead of lambdas, of course.
https://matplotlib.org/3.1.1/gallery/ticks_and_spines/tick-formatters.html
In matplotlib 3.1, you can also use ticklabel_format. To prevents scientific notation without offsets:
plt.gca().ticklabel_format(axis='both', style='plain', useOffset=False)
Related
I found that it is not easy to make annotation with x axis in format of timestamp.
Here is an example.
import mpld3
import pandas as pd
import matplotlib.pyplot as plt
mpld3.enable_notebook() ## I want to apply mpld3 for interactive jupyter notebook.
df = pd.read_csv("./test_file.csv")
fig = plt.figure()
ax = plt.subplot()
ax.plot(df['time'],df['HROrg'])
ax.annotate('note', xy = (df['time'].iloc[45],df['HROrg'].iloc[45]),xycoords='data')
It showed figure but with an error:
TypeError: Object of type Timestamp is not JSON serializable.
But when I changed to
ax.annotate('note', xy = (0.4,df['HROrg'].iloc[45]),xycoords=('axes fraction','data',))
The figure could show as normal.
Then, I thought that I could transform the datetime where I want to put the annotation to axes fraction.
def datetime_to_float(d):
return d.timestamp()
fig = plt.figure()
ax = plt.subplot()
ax.plot(df['time'],df['HROrg'])
xmin,xmax = ax.get_xlim()
x_ratio = (datetime_to_float(df['time'].iloc[45]) - xmin)/(xmax - xmin)
ax.annotate('note', xy = (x_ratio,df['HROrg'].iloc[45]),xycoords=('axes fraction','data',))
It shows the error:
Image size of 1349952799x248 pixels is too large. It must be less than 2^16 in each direction.
It seems like that the float timestamp that I transformed was not matched with te scale of xmin and xmax.
print (datetime_to_float(df['time'].iloc[45]), xmin)
> 1642771640.0 19013.544278935184
Thus, my question is what is the correct method to transform the datetime that matched with xmin and xmax? Any advices or comments would be highly appreciated!
The test file can be accessed at link here
Below is the plot I generated using axes.text option,
ax[0].text(row.TIMESTAMP, row.HIGH+(0.1*width),row['candlestick_pattern'], fontsize=5, rotation='vertical')
I'm trying to achieve the same output using TextPath and PathPatch, in order to increase/decrease the font size when I zoom in/out of the plot, and below is the code I have (taken from here and here )
textPath = TextPath((data_coord[1], -data_coord[0]), row['candlestick_pattern'], size=2)
pathPatch = PathPatch(textPath, color="black")
transform = mpl.transforms.Affine2D().rotate_deg(90) + ax[0].transData
pathPatch.set_transform(transform)
ax[0].add_patch(pathPatch)
Output with this is
You could see that the text is cramped into a very small region and its not what I want. I would want to set the font size to a smaller value and increase the width (in vertical mode - height) of the TextPath. Is that possible?
Below is the complete code with which we can reproduce the problem for the dataset here
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.textpath import TextPath
from matplotlib.patches import PathPatch
from mplfinance.original_flavor import candlestick_ohlc
from matplotlib import transforms as tf
import pandas as pd
plotDf = pd.read_csv("data.csv")
plotDf.reset_index(inplace=True)
del plotDf['TIMESTAMP']
del plotDf['TOTTRDQTY']
fig, ax = plt.subplots(1)
candlestick_ohlc(ax,plotDf.values,width=0.6, \
colorup='green', colordown='red', alpha=0.8)
maxHigh = plotDf['HIGH'].max()
minLow = plotDf['LOW'].min()
width = maxHigh - minLow
threshold = (width)*0.6
for idx, row in plotDf.iterrows():
if (row['candlestick_pattern'] != 'NO_PATTERN'):
if (row.HIGH < (threshold+minLow)):
data_coord = (idx, row.HIGH+(0.1*width))
#ax.text(idx, row.HIGH+(0.1*width), row['candlestick_pattern'], fontsize=5, rotation='vertical')
else:
data_coord = (idx, row.LOW-(0.4*width))
#ax.text(idx, row.LOW-(0.4*width), row['candlestick_pattern'], fontsize=5, rotation='vertical')
textPath = TextPath((data_coord[1], -data_coord[0]), row['candlestick_pattern'], size=2)
pathPatch = PathPatch(textPath, color="black")
transform = mpl.transforms.Affine2D().rotate_deg(90) + ax.transData
pathPatch.set_transform(transform)
ax.add_patch(pathPatch)
fig.autofmt_xdate()
fig.tight_layout()
fig.suptitle("test", fontsize=16)
fig.set_size_inches(10.5, 10.5)
plt.subplots_adjust(top=0.95)
plt.show()
Apparently, your problem is a scaling problem. Messing around with .scale(x,y), ax.set_xlim and ax.set_ylim might allow you to "unsqueeze" the text. You can also try to set an anchor for your plot like done here:
ts = ax.transData
coords = ts.transform([0,0]) #<-anchor
tr = mpl.transforms.Affine2D().rotate_deg_around(coords[0],coords[1],90).scale(1,3) #<- scale
t = ts + tr
#<extra code>
pathPatch = PathPatch(textPath, color="black", transform = t)
EDIT
I tried many things, but I couldn't find a good way of doing it. I'll leave below what I tried and some resources that might help.
The way to properly use .rotate_deg_around would be like such:
ts = ax.transData
# ts = fig.dpi_scale_trans #this guy uses the fig scale, if you're interested
coords = ts.transform([data_coord[0],data_coord[1]])
converter = (coords[0]/data_coord[0], coords[1]/data_coord[1])
#plot the anchor points for visualization:
plt.plot(coords[0]/converter[0], coords[1]/converter[1],'xk')
tr = mpl.transforms.Affine2D().rotate_deg_around(coords[0]/converter[0],coords[1]/converter[1],90).scale(converter[0],converter[1])
pathPatch = PathPatch(textPath, color="black", transform = tr)
ax.add_patch(pathPatch)
Nonetheless, the results are still similar to what you had at the beginning:
It appears that TextPath does not behave like it should when using transform. Here .get_offset_transform is used, and it apparently fixes this sort of issue, but I was unable to use it since the plt has a Line type.
Also, you will see that if you increase the y axis in .scale, you can start to see the text, but it spreads the coordinates as well. One idea you can try is setting a good readable y scale (use ax.set_ylim to see your text) and then use that value as a divisor when setting the coordinates for your plot.
There are also some ideas here that might serve you.
I am trying to customise a colorbar on my matpllotlib contourf plots. Whilst I am able to use scientific notation I am trying to change the base of the notation - essentially so that my ticks would be in the range of (-100,100) rather than (-10,10).
For example, this produces a simple plot...
import numpy as np
import matplotlib.pyplot as plt
z = (np.random.random((10,10)) - 0.5) * 0.2
fig, ax = plt.subplots()
plot = ax.contourf(z)
cbar = fig.colorbar(plot)
cbar.formatter.set_powerlimits((0, 0))
cbar.update_ticks()
plt.show()
like so:
However, I would like the label above the colorbar to be 1e-2 and the numbers to range from -10 to 10.
How would I go about this?
A possible solution can be to subclass the ScalarFormatter and fix the order of magnitude as in this question: Set scientific notation with fixed exponent and significant digits for multiple subplots
You would then call this formatter with the order of magnitude as the argument order, OOMFormatter(-2, mathText=False). mathText is set to false to obtain the notation from the question, i.e.
while setting it to True, would give .
You can then set the formatter to the colorbar via the colorbar's format argument.
import numpy as np; np.random.seed(0)
import matplotlib.pyplot as plt
import matplotlib.ticker
class OOMFormatter(matplotlib.ticker.ScalarFormatter):
def __init__(self, order=0, fformat="%1.1f", offset=True, mathText=True):
self.oom = order
self.fformat = fformat
matplotlib.ticker.ScalarFormatter.__init__(self,useOffset=offset,useMathText=mathText)
def _set_order_of_magnitude(self):
self.orderOfMagnitude = self.oom
def _set_format(self, vmin=None, vmax=None):
self.format = self.fformat
if self._useMathText:
self.format = r'$\mathdefault{%s}$' % self.format
z = (np.random.random((10,10)) - 0.5) * 0.2
fig, ax = plt.subplots()
plot = ax.contourf(z)
cbar = fig.colorbar(plot, format=OOMFormatter(-2, mathText=False))
plt.show()
For matplotlib versions < 3.1 the class needs to look like this:
class OOMFormatter(matplotlib.ticker.ScalarFormatter):
def __init__(self, order=0, fformat="%1.1f", offset=True, mathText=True):
self.oom = order
self.fformat = fformat
matplotlib.ticker.ScalarFormatter.__init__(self,useOffset=offset,useMathText=mathText)
def _set_orderOfMagnitude(self, nothing):
self.orderOfMagnitude = self.oom
def _set_format(self, vmin, vmax):
self.format = self.fformat
if self._useMathText:
self.format = '$%s$' % matplotlib.ticker._mathdefault(self.format)
Similar to what #ImportanceOfBeingErnes described, you could use a FuncFormatter (docs) to which you just pass a function to determine the tick labels. This removes the auto generation of the 1e-2 header for your colorbar, but I imagine you can manually add that back in (I had trouble doing it, though was able to add it on the side). Using a FuncFormatter, you can just generate string tick values which has the advantage of not having to accept the way python thinks a number should be displayed.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as tk
z = (np.random.random((10,10)) - 0.5) * 0.2
levels = list(np.linspace(-.1,.1,9))
fig, ax = plt.subplots()
plot = ax.contourf(z, levels=levels)
def my_func(x, pos):
label = levels[pos]
return str(label*100)
fmt1 = tk.FuncFormatter(my_func)
cbar = fig.colorbar(plot, format=fmt1)
cbar.set_label("1e-2")
plt.show()
This will generate a plot which looks like this.
I've spent some time fruitlessly searching for an answer to my question, so I think a new question is in order. Consider this plot:
The axes labels use scientific notation. On the y-axis, all is well. However, I have tried and failed to get rid off the scaling factor that Python added in the lower-right corner. I would like to either remove this factor completely and simply indicate it by the units in the axis title or have it multiplied to every tick label. Everything would look better than this ugly 1e14.
Here's the code:
import numpy as np data_a = np.loadtxt('exercise_2a.txt')
import matplotlib as mpl
font = {'family' : 'serif',
'size' : 12}
mpl.rc('font', **font)
import matplotlib.pyplot as plt
fig = plt.figure()
subplot = fig.add_subplot(1,1,1)
subplot.plot(data_a[:,0], data_a[:,1], label='$T(t)$', linewidth=2)
subplot.set_yscale('log')
subplot.set_xlabel("$t[10^{14}s]$",fontsize=14)
subplot.set_ylabel("$T\,[K]$",fontsize=14)
plt.xlim(right=max(data_a [:,0]))
plt.legend(loc='upper right')
plt.savefig('T(t).pdf', bbox_inches='tight')
Update: Incorporating Will's implementation of scientificNotation into my script, the plot now looks like
Much nicer if you ask me. Here's the complete code for anyone wanting to adopt some part of it:
import numpy as np
data = np.loadtxt('file.txt')
import matplotlib as mpl
font = {'family' : 'serif',
'size' : 16}
mpl.rc('font', **font)
import matplotlib.pyplot as plt
fig = plt.figure()
subplot = fig.add_subplot(1,1,1)
subplot.plot(data[:,0], data[:,1], label='$T(t)$', linewidth=2)
subplot.set_yscale('log')
subplot.set_xlabel("$t[s]$",fontsize=20)
subplot.set_ylabel("$T\,[K]$",fontsize=20)
plt.xlim(right=max(data [:,0]))
plt.legend(loc='upper right')
def scientificNotation(value):
if value == 0:
return '0'
else:
e = np.log10(np.abs(value))
m = np.sign(value) * 10 ** (e - int(e))
return r'${:.0f} \cdot 10^{{{:d}}}$'.format(m, int(e))
formatter = mpl.ticker.FuncFormatter(lambda x, p: scientificNotation(x))
plt.gca().xaxis.set_major_formatter(formatter)
plt.savefig('T(t).pdf', bbox_inches='tight', transparent=True)
Just divide the x-values by 1e14:
subplot.plot(data_a[:,0] / 1e14, data_a[:,1], label='$T(t)$', linewidth=2)
If you want to add the label to each individual tick, you'll have to provide a custom formatter, like in tom's answer.
If you want it to look like as nice as the ticks on your y-axis, you could provide a function to format it with LaTeX:
def scientificNotation(value):
if value == 0:
return '0'
else:
e = np.log10(np.abs(value))
m = np.sign(value) * 10 ** (e - int(e))
return r'${:.0f} \times 10^{{{:d}}}$'.format(m, int(e))
# x is the tick value; p is the position on the axes.
formatter = mpl.ticker.FuncFormatter(lambda x, p: scientificNotation(x))
plt.gca().xaxis.set_major_formatter(formatter)
Of course, this will clutter your x-axis up quite a bit, so you might end up needing to display them at an angle, for example.
You can also change the tick formatter with the ticker module.
An example would be to use a FormatStrFormatter:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig,ax = plt.subplots()
ax.semilogy(np.linspace(0,5e14,50),np.logspace(3,7,50),'b-')
ax.xaxis.set_major_formatter(ticker.FormatStrFormatter('%.0e'))
Also see the answers here with lots of good ideas for ways to solve this.
In addition to the good answer from Will Vousden, you can set what you write in your ticks with:
plt.xticks(range(6), range(6))
the first range(6) is the location and the second is the label.
How does one set the color of a line in matplotlib with scalar values provided at run time using a colormap (say jet)? I tried a couple of different approaches here and I think I'm stumped. values[] is a storted array of scalars. curves are a set of 1-d arrays, and labels are an array of text strings. Each of the arrays have the same length.
fig = plt.figure()
ax = fig.add_subplot(111)
jet = colors.Colormap('jet')
cNorm = colors.Normalize(vmin=0, vmax=values[-1])
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet)
lines = []
for idx in range(len(curves)):
line = curves[idx]
colorVal = scalarMap.to_rgba(values[idx])
retLine, = ax.plot(line, color=colorVal)
#retLine.set_color()
lines.append(retLine)
ax.legend(lines, labels, loc='upper right')
ax.grid()
plt.show()
The error you are receiving is due to how you define jet. You are creating the base class Colormap with the name 'jet', but this is very different from getting the default definition of the 'jet' colormap. This base class should never be created directly, and only the subclasses should be instantiated.
What you've found with your example is a buggy behavior in Matplotlib. There should be a clearer error message generated when this code is run.
This is an updated version of your example:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cm as cmx
import numpy as np
# define some random data that emulates your indeded code:
NCURVES = 10
np.random.seed(101)
curves = [np.random.random(20) for i in range(NCURVES)]
values = range(NCURVES)
fig = plt.figure()
ax = fig.add_subplot(111)
# replace the next line
#jet = colors.Colormap('jet')
# with
jet = cm = plt.get_cmap('jet')
cNorm = colors.Normalize(vmin=0, vmax=values[-1])
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet)
print scalarMap.get_clim()
lines = []
for idx in range(len(curves)):
line = curves[idx]
colorVal = scalarMap.to_rgba(values[idx])
colorText = (
'color: (%4.2f,%4.2f,%4.2f)'%(colorVal[0],colorVal[1],colorVal[2])
)
retLine, = ax.plot(line,
color=colorVal,
label=colorText)
lines.append(retLine)
#added this to get the legend to work
handles,labels = ax.get_legend_handles_labels()
ax.legend(handles, labels, loc='upper right')
ax.grid()
plt.show()
Resulting in:
Using a ScalarMappable is an improvement over the approach presented in my related answer:
creating over 20 unique legend colors using matplotlib
I thought it would be beneficial to include what I consider to be a more simple method using numpy's linspace coupled with matplotlib's cm-type object. It's possible that the above solution is for an older version. I am using the python 3.4.3, matplotlib 1.4.3, and numpy 1.9.3., and my solution is as follows.
import matplotlib.pyplot as plt
from matplotlib import cm
from numpy import linspace
start = 0.0
stop = 1.0
number_of_lines= 1000
cm_subsection = linspace(start, stop, number_of_lines)
colors = [ cm.jet(x) for x in cm_subsection ]
for i, color in enumerate(colors):
plt.axhline(i, color=color)
plt.ylabel('Line Number')
plt.show()
This results in 1000 uniquely-colored lines that span the entire cm.jet colormap as pictured below. If you run this script you'll find that you can zoom in on the individual lines.
Now say I want my 1000 line colors to just span the greenish portion between lines 400 to 600. I simply change my start and stop values to 0.4 and 0.6 and this results in using only 20% of the cm.jet color map between 0.4 and 0.6.
So in a one line summary you can create a list of rgba colors from a matplotlib.cm colormap accordingly:
colors = [ cm.jet(x) for x in linspace(start, stop, number_of_lines) ]
In this case I use the commonly invoked map named jet but you can find the complete list of colormaps available in your matplotlib version by invoking:
>>> from matplotlib import cm
>>> dir(cm)
A combination of line styles, markers, and qualitative colors from matplotlib:
import itertools
import matplotlib as mpl
import matplotlib.pyplot as plt
N = 8*4+10
l_styles = ['-','--','-.',':']
m_styles = ['','.','o','^','*']
colormap = mpl.cm.Dark2.colors # Qualitative colormap
for i,(marker,linestyle,color) in zip(range(N),itertools.product(m_styles,l_styles, colormap)):
plt.plot([0,1,2],[0,2*i,2*i], color=color, linestyle=linestyle,marker=marker,label=i)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.,ncol=4);
UPDATE: Supporting not only ListedColormap, but also LinearSegmentedColormap
import itertools
import matplotlib.pyplot as plt
Ncolors = 8
#colormap = plt.cm.Dark2# ListedColormap
colormap = plt.cm.viridis# LinearSegmentedColormap
Ncolors = min(colormap.N,Ncolors)
mapcolors = [colormap(int(x*colormap.N/Ncolors)) for x in range(Ncolors)]
N = Ncolors*4+10
l_styles = ['-','--','-.',':']
m_styles = ['','.','o','^','*']
fig,ax = plt.subplots(gridspec_kw=dict(right=0.6))
for i,(marker,linestyle,color) in zip(range(N),itertools.product(m_styles,l_styles, mapcolors)):
ax.plot([0,1,2],[0,2*i,2*i], color=color, linestyle=linestyle,marker=marker,label=i)
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.,ncol=3,prop={'size': 8})
U may do as I have written from my deleted account (ban for new posts :( there was). Its rather simple and nice looking.
Im using 3-rd one of these 3 ones usually, also I wasny checking 1 and 2 version.
from matplotlib.pyplot import cm
import numpy as np
#variable n should be number of curves to plot (I skipped this earlier thinking that it is obvious when looking at picture - sorry my bad mistake xD): n=len(array_of_curves_to_plot)
#version 1:
color=cm.rainbow(np.linspace(0,1,n))
for i,c in zip(range(n),color):
ax1.plot(x, y,c=c)
#or version 2: - faster and better:
color=iter(cm.rainbow(np.linspace(0,1,n)))
c=next(color)
plt.plot(x,y,c=c)
#or version 3:
color=iter(cm.rainbow(np.linspace(0,1,n)))
for i in range(n):
c=next(color)
ax1.plot(x, y,c=c)
example of 3:
Ship RAO of Roll vs Ikeda damping in function of Roll amplitude A44