Tick label text and frequency in matplotlib plot - python

I want to plot some data stored in a Pandas Dataframe using matplotlib. I want to put specific labels on x axis ticks. So, I set them with:
ax.xaxis.set_ticklabels(data_frame['labels'])
That works well, but it sets a tick label for each data point, making the plot unreadable, so I tried:
ax.locator_params(axis='x', nbins=3)
which reduces the number of ticks to 3, but the labels are not corresponding to correct data points (if labels are a,b,c,d,e ..., x,y,z I get labels a,b,c instead of a,m,z or something like that). My next idea was to set tick labels positions:
ax.xaxis.set_ticks(data_frame.index.values)
but it does not work.
What works is:
ax.xaxis.set_ticklabels(data_frame['labels'][::step])
ax.xaxis.set_ticks(data_frame.index.values[::step])
without setting any locator_params.
This is almost perfect. It fixes the ticks and labels, but when I zoom the plot (using the matplotlib interactive window) new labels are obviously not appearing. And what I need are readable ticks that adjust themselves depending on plot zoom (this is what ax.locator_params(axis='x', nbins=3) does correctly without any custom labels).
In other words: I need to set specific label for each data point but show only few of them on the plot axis ticks without losing the correct assignment.

Using Locator we can define how many ticks shall be produced and where they should be placed. By sub-classing MaxNLocator (this is essentially the default Locator) we can reuse the functionality and simply filter out unwanted ticks (e.g. ticks outside the label range). My approach could definitely be improved at this point, as sparse or non-equidistant x-range data would break my simple filtering solution. Also float values might be a challenge, but I'm certain such a data range could always be mapped to a convenient integer range if the above conditions do not apply. But this is beyond the scope of this question.
With Formatter we can now simply lookup the corresponding labels in our label list to produce the correct tick label. For finding the closest matching value, we can efficiently utilize the bisect module (related question). For static plots we could rely on the assumption that our Locator already produces indices we can directly use for our list access (avoiding unnecessary bisect operation). However, the dynamic view (see the bottom left corner in the screenshots) uses the Formatter to format non-tick position labels. Thus, using bisect is the more general and stable approach.
import matplotlib.pyplot as plt
import numpy as np
import bisect
from matplotlib.ticker import Formatter
from matplotlib.ticker import MaxNLocator
x = np.arange(0, 100, 1)
y = np.sin(x)
# custom labels, could by anything
l = ["!{}!".format(v) for v in x]
plt.plot(x, y)
ax = plt.gca()
class LookupLocator(MaxNLocator):
def __init__(self, valid_ticks, nbins='auto', min_n_ticks=0, integer=True):
MaxNLocator.__init__(self, integer=integer, nbins=nbins, min_n_ticks=min_n_ticks)
self._valid_ticks = valid_ticks
self._integer = integer
def is_tick_valid(self, t):
if self._integer:
return t.is_integer() and int(t) in self._valid_ticks
return t in self._valid_ticks
def tick_values(self, vmin, vmax):
return filter(self.is_tick_valid, MaxNLocator.tick_values(self, vmin, vmax))
class LookupFormatter(Formatter):
def __init__(self, tick_values, tick_labels):
Formatter.__init__(self)
self._tick_values = tick_values
self._tick_labels = tick_labels
def _find_closest(self, x):
# https://stackoverflow.com/questions/12141150/from-list-of-integers-get-number-closest-to-a-given-value
i = bisect.bisect_left(self._tick_values, x)
if i == 0:
return i
if i == len(self._tick_values):
return i - 1
l, r = self._tick_values[i - 1], self._tick_values[i]
if l - x < x - r:
return i
return i - 1
def __call__(self, x, pos=None):
return self._tick_labels[self._find_closest(x)]
ax.xaxis.set_major_locator(LookupLocator(x))
ax.xaxis.set_major_formatter(LookupFormatter(x, l))
plt.show()

Related

Custom color mapping based on function in matplotlib

I'm plotting a scatterplot using matplotlib in python. I want to color the points based on some function, like so:
import matplotlib.pyplot as plt
def color(x, y):
# based on some rules, return a color
if(condition):
return 'red'
else:
return 'blue'
plt.scatter(index, data) #c= something?
I'm aware of the matplotlib.from_levels_and_colors function, but the problem is that the mapping isn't based on levels of the values on the x or y axes. There's a third value associated with each data point that is calculated by the function, and that's what I want to color the dots based on.
Is there a way to do this?
Why don't you just make your c array an indicator function for a default colormap. For example:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(100)
y = np.arange(100)
# Colors whether or not x+y is a multiple of 5
c = (x + y)%5 == 0
# Use 'jet' colormap for red/blue.
plt.scatter(x, y, c=c, cmap='jet')
Of course you could use a colormap with different extremes that will get mapped to 0 and 1.
If your desired result has more than 2 colors, then it's totally fine to pass as c an array with many different values (and it doesn't need to be normalized). See here for an example.
This is the way I ended up doing it. I created a list of colors based on the x and y values, and then passed that to the scatter function. Not as nice as wflynny's answer, but it does mean you can do as much computation as needed to come up with the array, rather than having to create a single function to do it.
import matplotlib.pyplot as plt
colors = calculate_colors(x, y)
plt.scatter(index, data, c=colors)

matplotlib - updating existing plots with points

I have a function wrapper for making a plot in matplotlib. i want to know how best we return the figure handle from inside the function. I want to use the figure handle to update the plot by putting more points on it. The size of the points should depend on it's value of the data point. The bigger the data point, the bigger the size of the point.
One common way is to return an Axes object from your function. You can do additional plotting directly from the Axes.
You don't say whether your function is using the pyplot state machine or bare-bones Matplotlib, but here's an example of the former:
import matplotlib.pyplot as plt
x = range(3)
y1 = [2, 1, 3]
y2 = [3, 2, 1]
def plot_data(x, y):
"""Plots x, y. Returns the Axes."""
plt.plot(x, y, '-.k')
return plt.gca()
ax = plot_data(x, y1)
ax.scatter(x, y2, s=y2)
Here we also use the s= argument to specify the size of each point. Matplotlib assumes certain units for these values so you may end up having to multiply by some constant to scale them to meet your aesthetics.
Note that in addition to returning the Axes, sometimes it's useful to also have your plotting function also take an existing Axes as the argument.

How to do a range bar graph in matplotlib?

I'm trying to make a plot using matplotlib that resembles the following:
However, I'm not quite sure which type of graph to use. My data has the following form, where start x position is a positive value greater or equal to 0:
<item 1><start x position><end x position>
<item 2><start x position><end x position>
Looking at the docs, I see that there is barh and errorbar, but I'm not sure if its possible to use barh with a start offset. What would be the best method to use, given my type of data? I'm not that familiar with the library, so I was hoping to get some insight.
Appetizer
Commented Code
As far as I know, the most direct way to do what you want requires that you directly draw your rectangles on the matplotlib canvas using the patches module of matplotlib
A simple implementation follows
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def plot_rect(data, delta=0.4):
"""data is a dictionary, {"Label":(low,hi), ... }
return a drawing that you can manipulate, show, save etc"""
yspan = len(data)
yplaces = [.5+i for i in range(yspan)]
ylabels = sorted(data.keys())
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_yticks(yplaces)
ax.set_yticklabels(ylabels)
ax.set_ylim((0,yspan))
# later we'll need the min and max in the union of intervals
low, hi = data[ylabels[0]]
for pos, label in zip(yplaces,ylabels):
start, end = data[label]
ax.add_patch(patches.Rectangle((start,pos-delta/2.0),end-start,delta))
if start<low : low=start
if end>hi : hi=end
# little small trick, draw an invisible line so that the x axis
# limits are automatically adjusted...
ax.plot((low,hi),(0,0))
# now get the limits as automatically computed
xmin, xmax = ax.get_xlim()
# and use them to draw the hlines in your example
ax.hlines(range(1,yspan),xmin,xmax)
# the vlines are simply the x grid lines
ax.grid(axis='x')
# eventually return what we have done
return ax
# this is the main script, note that we have imported pyplot as plt
# the data, inspired by your example,
data = {'A':(1901,1921),
'B':(1917,1935),
'C':(1929,1948),
'D':(1943,1963),
'E':(1957,1983),
'F':(1975,1991),
'G':(1989,2007)}
# call the function and give its result a name
ax = plot_rect(data)
# so that we can further manipulate it using the `axes` methods, e.g.
ax.set_xlabel('Whatever')
# finally save or show what we have
plt.show()
The result of our sufferings has been shown in the first paragraph of this post...
Addendum
Let's say that you feel that blue is a very dull color...
The patches you've placed in your drawing are accessible as a property (aptly named patches...) of the drawing and modifiable too, e.g.,
ax = plot_rect(data)
ax.set_xlabel('Whatever')
for rect in ax.patches:
rect.set(facecolor=(0.9,0.9,0.2,1.0), # a tuple, RGBA
edgecolor=(0.6,0.2,0.3,1.0),
linewidth=3.0)
plt.show()
In my VH opinion, a custom plotting function should do the least indispensable to characterize the plot, as this kind of post-production is usually very easy in matplotlib.

How do you generate an animated square wave from binary number for the respective decimal number using for loop

I am using the following codes to generate the square wave format [Eg: from 0 till 5] using for loop. I am able to print the respective binary values but not able to plot the square wave dynamically.In addition to this I am not able to dynamically resize the x axis in the plot window. I could not find any suitable code at Matplotlib animation section. Could any one help me in this?
import numpy as np
import matplotlib.pyplot as plt
limit=int(raw_input('Enter the value of limit:'))
for x in range(0,limit):
y=bin(x)[2:]
data = [int(i) for i in y]
print data
xs = np.repeat(range(len(data)),2)
ys = np.repeat(data, 2)
xs = xs[1:]
ys = ys[:-1]
plt.plot(xs, ys)
plt.xlim(0,len(data)+0.5)
plt.ylim(-2, 2)
plt.grid()
plt.show()
#plt.hold(True)
#plt.pause(0.5)
plt.clf()
Your question as stated is pretty vague so I'm going to I'm going to go out on a limb and assume that what you want is to plot a series of equal length binary codes using the same figure with some delay in between.
So, two problems here:
Generating the appropriate binary codes
Plotting those codes successively
1. Generating the appropriate binary codes
From what I can reasonably guess, you want to plot binary codes of the same length. So you'll have to zero pad your codes so they are the same length. One way to do this is with python's built in zfill function.
e.g.
bin(1).zfill(4)
This also brings light to the fact that you will have to know the length of the largest binary string you want to plot if you want to keep the x-axis range constant. Since it's not clear if you even want constant length strings I'll just leave it at this.
2. Plotting those codes successively
There are a couple different ways to create animations in matplotlib. I find manually updating data is a little bit more flexible and less buggy than the animations API currently is so I will be doing that here. I've also cut down some parts of the code that were not clear to me.
Here's a simple a implementation:
import matplotlib.pyplot as plt
import numpy as np
# Enable interactive plotting
plt.ion()
# Create a figure and axis for plotting on
fig = plt.figure()
ax = fig.add_subplot(111)
# Add the line 'artist' to the plotting axis
# use 'steps' to plot binary codes
line = plt.Line2D((),(),drawstyle='steps-pre')
ax.add_line(line)
# Apply any static formatting to the axis
ax.grid()
ax.set_ylim(-2, 2)
# ...
try:
limit = int(raw_input('Enter the value of limit:'))
codelength = int(np.ceil(np.log2(limit)))+1 # see note*
ax.set_xlim(0,codelength)
for x in range(0,limit):
# create your fake data
y = bin(x)[2:].zfill(codelength)
data = [int(i) for i in y]
print data
xs = range(len(data))
line.set_data(xs,data) # Update line data
fig.canvas.draw() # Ask to redraw the figure
# Do a *required* pause to allow figure to redraw
plt.pause(2) # delay in seconds between frames
except KeyboardInterrupt: # allow user to escape with Ctrl+c
pass
finally: # Always clean up!
plt.close(fig)
plt.ioff()
del ax,fig
Result
*Note: I padded the binary codes by an extra zero to get the plot to look right.

Contour labels in Python

I would like to plot a series of contour lines, but the spacing between where the label is and the line increases higher up the page. I've plotted an example attached. I want no decimal points, hence used fmt, but this seems to change the spacing at different points (Ideally I want around half a centimetre gap between the contour line break and the writing.
As an aside, I also tried to use the manual locations so it'd plot each label at a certain place, but as there are two seperate contour lines with the same value I'm not sure if this is possible. Thanks!
Here is my code;
from netCDF4 import Dataset
import numpy as np
from matplotlib import pyplot as plt
#############################
# #
# Parameter Setup #
# #
#############################
myfile = '/home/ubuntu/Documents/Control/puma/run/Control.nc' #Control U
myfile2 = '/home/ubuntu/Documents/Control_Trop_40K/puma/run/ControlTrop.nc' #Perturbed U
Title = 'U'
Units = '!U'
Variable = 'ua'
#############################
#############################
Import = Dataset(myfile, mode='r')
Import2 = Dataset(myfile2, more='r')
latt = Import.variables['lat'][:]
level = Import.variables['lev'][:]
UControl = Import.variables[Variable][:]
#UPerturbed = Import2.variables[Variable][:]
#UChange = UPerturbed - UControl
#UChange = np.mean(UChange[:,:,:,0], axis=0)
UControl = np.mean(UControl[:,:,:,0], axis=0)
Contourrange = [10]
CS=plt.contour(latt,level,UControl,Contourrange, colors='k')
plt.clabel(CS, fontsize=10, inline=1,fmt = '%1.0f',ticks=Contourrange)
plt.gca().invert_yaxis()
plt.yscale('log', nonposy='clip')
plt.xticks(np.round((np.arange(-90, 91, 30)), 2))
plt.xlim(xmin=-90)
plt.yticks([900,800,700,600,500,400,300,200,100,50])
plt.gca().set_yticklabels([900,800,700,600,500,400,300,200,100,50])
plt.xlabel('Latitude')
plt.ylabel('Pressure (hPa)')
plt.title(Title)
plt.show()
The pictures are:
You are manually defining the values for which it should be a tick:
plt.yticks([900,800,700,600,500,400,300,200,100,50])
Since you also have chosen a logarithmic scale, and since the increment you specified is constant, matplotlib needs to vary the space between ticks to comply with both your requirements.
If you absolutely do not want this behavior, either get rid of the log option, or let matplotlib automatically set ticks for you. Alternatively, you could provide the plt.yticks fuction with an array of exponentially increasing/decreasing numbers. Like this:
plt.yticks([10^3,10^2,10^1])
You will have to make sure you are using the correct base (I simply assumed a base 10), and you will have to find suitable numbers to span your range of values.

Categories