How to show min and max values at the end of the axes - matplotlib

I generate plots like below:
from pylab import *
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker
import matplotlib.ticker as ticker
rcParams['axes.linewidth'] = 2 # set the value globally
rcParams['font.size'] = 16# set the value globally
rcParams['font.family'] = ['DejaVu Sans']
rcParams['mathtext.fontset'] = 'stix'
rcParams['legend.fontsize'] = 24
rcParams['axes.prop_cycle'] = cycler(color=['grey','b','g','r','orange'])
rc('lines', linewidth=2, linestyle='-',marker='o')
rcParams['axes.xmargin'] = 0
rcParams['axes.ymargin'] = 0
t = arange(0,21,1)
v = 2.0
s = v*t
plt.figure(figsize=(12, 4))
plt.plot(t,s,label='$s=%1.1f\cdot t$'%v)
plt.title('Wykres drogi w czasie $s=v\cdot t$')
plt.xlabel('Czas $t$, s')
plt.ylabel('Droga $s$, m')
plt.autoscale(enable=True, axis='both', tight=None)
legend(loc='best')
plt.xlim(min(t),max(t))
plt.ylim(min(s),max(s))
plt.grid()
plt.show()
When I am changing the value t = arange(0,21,1) for example to t = arange(0,20,1) which gives me for example on the x axis max value= 19.0 my max value dispirs from the x axis. The same situation is of course with y axis.
My question is how to force matplotlib to produce always plots where on the axes are max values just at the end of the axes like should be always for my purposes or should be possible to chose like an option?
Imiage from my program in Fortan I did some years ago
Matplotlib is more efficiens that I use it but there should be an opition like that (the picture above).
In this way I can always observe max min in text windows or do take addiional steps to make sure about max min values. I would like to read them from axes and the question is ...Are there such possibilites in mathplotlib ??? If not I will close the post.
Axes I am thinking about more or less

I see two ways to solve the problem.
Set the axes automatic limit mode to round numbers
In the rcParams you can do this with
rcParams['axes.autolimit_mode'] = 'round_numbers'
And turn off the manual axes limits with min and max
plt.xlim(min(t),max(t))
plt.ylim(min(s),max(s))
This will produce the image below. Still, the extreme values of the axes are shown at the nearest "round numbers", but the user can approximately catch the data range limits. If you need the exact value to be displayed, you can see the second solution which cannot be directly used from the rcParams.
or – Manually generate axes ticks
This solution implies explicitly asking for a given number of ticks. I guess there is a way to automatize it depending on the axes size etc. But if you are dealing with more or less every time the same graph size, you can decide a fixed number of ticks manually. This can be done with
plt.xlim(min(t),max(t))
plt.ylim(min(s),max(s))
plt.xticks(np.linspace(t.min(), t.max(), 7)) # arbitrary chosen
plt.yticks(np.linspace(s.min(), s.max(), 5)) # arbitrary chosen
generated the image below, quite similar to your image example.

Related

How to plot a line that is partially colorized?

As shown in the figure,
How can I plot a line that have different colors based on a specific value of x ?
The simplest solution here may be to slice your data at the corresponding index of x_lim found by np.where :
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0,2*np.pi,100)
y = np.cos(x)*np.exp(-x/2)
# specify your x limitation
x_lim = np.pi
# find the first corresponding idx where the condition x>=x_lim hold
x_lim_idx = np.where(x>=x_lim)[0][0]
# plot sliced data
plt.plot(x[:x_lim_idx],y[:x_lim_idx],'r')
plt.plot(x[x_lim_idx:],y[x_lim_idx:],'b')
which gives for x_lim = np.pi :
And if the remaining gap between the lines bothers you, for small x discretization for instance, you can still close it by making the two slices overlap.

Add more deciamals to matplotlib chart?

Simple question and I tried a quick search before posting but could not find. I am trying to do a chart and axis Y consists of price.
However Y is scaled like attached image and has only 1 decimal. How do I make y axis more precise with 2 decimals and more entries with increment of 0.01?
::Update with code::
# Make the plot
fig, ax = plt.subplots(figsize=(48,32))
ax.scatter(x=times, y=tidy['Price'], c=colors, s=tidy['Volume'] / 4000, alpha=0.4)
ax.ticklabel_format(axis='y', style='plain')
ax.set(
xlabel='Time',
xlim=(xmin, xmax),
ylabel='Price'
)
ax.xaxis.set_major_formatter(DateFormatter('%H:%M'))
One method to increase the number of decimals is to use a formatter for your axis:
from matplotlib.ticker import FormatStrFormatter
ax.yaxis.set_major_formatter(FormatStrFormatter('%.2f'))
However, this method will not increase the number of ticks on your axis. You can set the yticks with .01 increments using the following but you might end up over-saturating the axis might want to increase the increment size.
ax.set_yticks(np.arange(108.30,108.71,.01))

Get real range in colormap with LogLocator

The following code
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import ticker
n = 50
A = np.tile(np.linspace(-26,-2,n),(n,1))
plt.figure()
plt.contourf(A)
plt.colorbar()
B = np.tile(np.logspace(-26,-2,n),(n,1))
plt.figure()
plt.contourf(B,locator=ticker.LogLocator())
plt.colorbar()
plt.show()
produces these two plots:
For the linear case (first image), every color in the colorbar is present in the image, and the min and max values of A lie respectively in the first and last color bin (going bottom to top).
For the log case (second image), the colorbar's min and max values don't make sense to me anymore.
The minimum of B is 10^-26, so this value lies at the border between the first and second color bin of the colormap, but there are none of these two first colors in the image.
The maximum of B is 10^-2, and it lies at the border between the before-before last, and the before last color bins, so it could be considered in either.
But then, why is the last (yellow) color bin here, especially since there is no yellow in the image ?
So I find the default behavior of the colormap limits (for the LogLocator) weird because it is not representative of the real (or at least approximate) data range (like in the linear case), and it adds color bins (in this case 3 : 2 below the min, and 1 above the max) that are not present in the image.
Is this a bug or is there something I didn't understand ?
#ImportanceOfBeingErnest's answer below gives the output that I want, but it just feels like I shouldn't have to do this and that I can expect the same behavior from the colormap with linear values, and from the LogLocator color mapper.
If you want to have specific intervals in your contour plot you would need to decide for them and supply them to the contouring function via the levels argument.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import ticker
n = 50
A = np.tile(np.logspace(-26,-2,n),(n,1))
levels = 10.**np.arange(-26,-1,4)
plt.figure()
plt.contourf(A,levels=levels, locator=ticker.LogLocator())
plt.colorbar()
plt.show()

How to change pyplot.specgram x and y axis scaling?

I have never worked with audio signals before and little do I know about signal processing. Nevertheless, I need to represent and audio signal using pyplot.specgram function from matplotlib library. Here is how I do it.
import matplotlib.pyplot as plt
import scipy.io.wavfile as wavfile
rate, frames = wavfile.read("song.wav")
plt.specgram(frames)
The result I am getting is this nice spectrogram below:
When I look at x-axis and y-axis which I suppose are frequency and time domains I can't get my head around the fact that frequency is scaled from 0 to 1.0 and time from 0 to 80k.
What is the intuition behind it and, what's more important, how to represent it in a human friendly format such that frequency is 0 to 100k and time is in sec?
As others have pointed out, you need to specify the sample rate, else you get a normalised frequency (between 0 and 1) and sample index (0 to 80k). Fortunately this is as simple as:
plt.specgram(frames, Fs=rate)
To expand on Nukolas answer and combining my Changing plot scale by a factor in matplotlib
and
matplotlib intelligent axis labels for timedelta
we can not only get kHz on the frequency axis, but also minutes and seconds on the time axis.
import matplotlib.pyplot as plt
import scipy.io.wavfile as wavfile
cmap = plt.get_cmap('viridis') # this may fail on older versions of matplotlib
vmin = -40 # hide anything below -40 dB
cmap.set_under(color='k', alpha=None)
rate, frames = wavfile.read("song.wav")
fig, ax = plt.subplots()
pxx, freq, t, cax = ax.specgram(frames[:, 0], # first channel
Fs=rate, # to get frequency axis in Hz
cmap=cmap, vmin=vmin)
cbar = fig.colorbar(cax)
cbar.set_label('Intensity dB')
ax.axis("tight")
# Prettify
import matplotlib
import datetime
ax.set_xlabel('time h:mm:ss')
ax.set_ylabel('frequency kHz')
scale = 1e3 # KHz
ticks = matplotlib.ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x/scale))
ax.yaxis.set_major_formatter(ticks)
def timeTicks(x, pos):
d = datetime.timedelta(seconds=x)
return str(d)
formatter = matplotlib.ticker.FuncFormatter(timeTicks)
ax.xaxis.set_major_formatter(formatter)
plt.show()
Result:
Firstly, a spectrogram is a representation of the spectral content of a signal as a function of time - this is a frequency-domain representation of the time-domain waveform (e.g. a sine wave, your file "song.wav" or some other arbitrary wave - that is, amplitude as a function of time).
The frequency values (y-axis, Hertz) are wholly dependant on the sampling frequency of your waveform ("song.wav") and will range from "0" to "sampling frequency / 2", with the upper limit being the "nyquist frequency" or "folding frequency" (https://en.wikipedia.org/wiki/Aliasing#Folding). The matplotlib specgram function will automatically determine the sampling frequency of the input waveform if it is not otherwise specified, which is defined as 1 / dt, with dt being the time interval between discrete samples of the waveform. You can can pass the option Fs='sampling rate' to the specgram function to manually define what it is. It will be easier for you to get your head around what is going on if you figure out and pass these variables to the specgram function yourself
The time values (x-axis, seconds) are purely dependent on the length of your "song.wav". You may notice some whitespace or padding if you use a large window length to calculate each spectra slice (think- the individual spectra which are arranged vertically and tiled horizontally to create the spectrogram image)
To make the axes more intuitive in the plot, use x- and y-axes labels and you can also scale the axes values (i.e. change the units) using a method similar to this
Take home message - try to be a bit more verbose with your code: see below for my example.
import matplotlib.pyplot as plt
import numpy as np
# generate a 5Hz sine wave
fs = 50
t = np.arange(0, 5, 1.0/fs)
f0 = 5
phi = np.pi/2
A = 1
x = A * np.sin(2 * np.pi * f0 * t +phi)
nfft = 25
# plot x-t, time-domain, i.e. source waveform
plt.subplot(211)
plt.plot(t, x)
plt.xlabel('time')
plt.ylabel('amplitude')
# plot power(f)-t, frequency-domain, i.e. spectrogram
plt.subplot(212)
# call specgram function, setting Fs (sampling frequency)
# and nfft (number of waveform samples, defining a time window,
# for which to compute the spectra)
plt.specgram(x, Fs=fs, NFFT=nfft, noverlap=5, detrend='mean', mode='psd')
plt.xlabel('time')
plt.ylabel('frequency')
plt.show()
5Hz_spectrogram:

matplotlib: preventing a few very large (or small) values to affect my contour

in plotting the data some times there are a few very large (or very small) numbers which, if not taken care of, will affect the contour in a bad way. a solution is to take out the 10% highest and lowest data out of the contour color grading and considering them as less than and more than. the following figure shows the idea:
the two arrow shapes on the top and the bottom of the bar support this idea. any value above 14 will be shown in white and any value below -2 will be shown in black color. how is it possible in matplotlib?
How can I define:
- to put the 5% of highest values and 5% of lowest values in two categories shown in the triangular parts in both ends of the bar? (Should I define it the contour operation or are there other ways?)
- what if I want to give certain values instead of the percentage? for instance, ask to put any value above 14 on the white triangule and any value below -2 as black areas?
Thank you so much for your help.
Taken from http://matplotlib.org/examples/api/colorbar_only.html. You can play with it and you will see if it could solve your problem.
import matplotlib.pyplot as plt
from matplotlib import mpl
import numpy as np
x = np.linspace(-1,1,100)
X,Y = np.meshgrid(x,x)
Z = np.exp(-X**2-Y**2)
vmin = 0.3 #Lower value
vmax = 0.9 #Upper value
bounds = np.linspace(vmin,vmax,4)
cmap = mpl.colors.ListedColormap([(0,0,0),(0.5,0.5,0.5),(0,1,0),(1,1,1)])
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
plt.imshow(Z,cmap=cmap,interpolation='nearest',vmin=vmin,vmax=vmax)
ax = plt.colorbar().ax
cb = mpl.colorbar.ColorbarBase(ax, norm=norm,
extend='both',
cmap=cmap)
cmap.set_over([0,0,1])
cmap.set_under([1,0,0])
plt.show()