I wish to plot the time variation of my y-axis variable using Matplotlib. This is no problem for continuously discrete data, however how should this be tackled for non-continuous data.
I.e. if I wanted to visualise the times at which my car was stationary on the way to work the x-axis would be time and the y-axis would be comprised of the variables 'stationary' and 'moving' (pretty useless example i know)
The non-continuous data would need to be indexed somehow, but i don't know how to proceed...any ideas?
Is this the type of thing you want? (If not, you might want to check out the matplotlib gallery page to give yourself some ideas, or maybe just draw a picture and post it.)
import matplotlib.pyplot as plt
data = [0]*5 + [1]*10 + [0]*3 +[1]*2
print data
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(data)
ax.set_yticks((0, 1.))
ax.set_yticklabels(('stopped', 'moving'))
ax.set_ybound((-.2, 1.2))
ax.set_xlabel("time (minutes)")
plt.show()
Related
I'm trying to create a video of many figures, so I need the axis to remain steady across multiple, independent figures. However, the y-axis changes scale, so the framing of the axis keeps moving as the ticklabels change. I'm trying to manually tell matplotlib exactly what size the whole figure should be and tell it exactly the position of the axis within the figure, but it's not working properly.
Here's what a base figure looks like:
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(8,4),facecolor=(0.5,0.5,0.5))
ax=fig.add_subplot()
ax.plot([5,10],[800,900])
plt.show()
Here is one way for how I'm trying to change it if I want the axis frame to start at left=0.5, bottom=0.5, width=0.2, and height=0.2. I've tried many different ways, and all have failed, so this is illustrative of what I'm trying to do:
fig=plt.figure(figsize=(8,4),facecolor=(0.5,0.5,0.5))
ax=fig.add_axes((0.5,0.5,0.2,0.2))
ax.plot([5,10],[800,900])
plt.show()
Now, I want it to look more like this so that the black box of the axis frame will be in the exact same position for every figure, and each figure will be the exact same size. That way, when I make it an animation, the black frame won't be jerking around. (Obviously, I wouldn't make the buffer that big in the real video.)
You need to use ax.set_position.
If your ax box initially occupies the full figure, you can create a new size relatively to the old one, for example:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8, 4), facecolor=(0.5, 0.5, 0.5))
ax = fig.add_subplot(111)
bbox = ax.get_position()
new_bbox = (bbox.x0+0.40, bbox.y0+0.40, bbox.width*0.5, bbox.height*0.5)
ax.set_position(new_bbox)
ax.plot([5, 10], [800, 900])
plt.show()
I am starting to play around with creating polar plots in Matplotlib that do NOT encompass an entire circle - i.e. a "wedge" plot - by setting the thetamin and thetamax properties. This is something I was waiting for for a long time, and I am glad they have it done :)
However, I have noticed that the figure location inside the axes seem to change in a strange manner when using this feature; depending on the wedge angular aperture, it can be difficult to fine tune the figure so it looks nice.
Here's an example:
import numpy as np
import matplotlib.pyplot as plt
# get 4 polar axes in a row
fig, axes = plt.subplots(2, 2, subplot_kw={'projection': 'polar'},
figsize=(8, 8))
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig.set_facecolor('paleturquoise')
for i, theta_max in enumerate([2*np.pi, np.pi, 2*np.pi/3, np.pi/3]):
# define theta vector with varying end point and some data to plot
theta = np.linspace(0, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
axes[i//2, i%2].set_thetamin(0)
axes[i//2, i%2].set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
axes[i//2, i%2].plot(theta, data)
axes[i//2, i%2].set_ylim([0, 1])
axes[i//2, i%2].set_xlabel('Magnitude', fontsize=15)
axes[i//2, i%2].set_ylabel('Angles', fontsize=15)
fig.set_tight_layout(True)
#fig.savefig('fig.png', facecolor='skyblue')
The labels are in awkward locations and over the tick labels, but can be moved closer or further away from the axes by adding an extra labelpad parameter to set_xlabel, set_ylabel commands, so it's not a big issue.
Unfortunately, I have the impression that the plot is adjusted to fit inside the existing axes dimensions, which in turn lead to a very awkward white space above and below the half circle plot (which of course is the one I need to use).
It sounds like something that should be reasonably easy to get rid of - I mean, the wedge plots are doing it automatically - but I can't seem to figure it out how to do it for the half circle. Can anyone shed a light on this?
EDIT: Apologies, my question was not very clear; I want to create a half circle polar plot, but it seems that using set_thetamin() you end up with large amounts of white space around the image (especially above and below) which I would rather have removed, if possible.
It's the kind of stuff that normally tight_layout() takes care of, but it doesn't seem to be doing the trick here. I tried manually changing the figure window size after plotting, but the white space simply scales with the changes. Below is a minimum working example; I can get the xlabel closer to the image if I want to, but saved image file still contains tons of white space around it.
Does anyone knows how to remove this white space?
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, subplot_kw={'projection': 'polar'})
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig1.set_facecolor('skyblue')
theta_min = 0
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15)
ax1.set_ylabel('Angles', fontsize=15)
fig1.set_tight_layout(True)
#fig1.savefig('fig1.png', facecolor='skyblue')
EDIT 2: Added background color to figures to better show the boundaries, as suggested in ImportanteOfBeingErnest's answer.
It seems the wedge of the "truncated" polar axes is placed such that it sits in the middle of the original axes. There seems so be some constructs called LockedBBox and _WedgeBbox in the game, which I have never seen before and do not fully understand. Those seem to be created at draw time, such that manipulating them from the outside seems somewhere between hard and impossible.
One hack can be to manipulate the original axes such that the resulting wedge turns up at the desired position. This is not really deterministic, but rather looking for some good values by trial and error.
The parameters to adjust in this case are the figure size (figsize), the padding of the labels (labelpad, as already pointed out in the question) and finally the axes' position (ax.set_position([left, bottom, width, height])).
The result could then look like
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, figsize=(6,3.4), subplot_kw={'projection': 'polar'})
theta_min = 1.e-9
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6.)*np.abs(np.sin(3*theta)/np.sin(theta/2.))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180./np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15, labelpad=-60)
ax1.set_ylabel('Angles', fontsize=15)
ax1.set_position( [0.1, -0.45, 0.8, 2])
plt.show()
Here I've set some color to the background of the figure to better see the boundary.
I have to plot several curves with very high xtick density, say 1000 date strings. To prevent these tick labels overlapping each other I manually set them to be 60 dates apart. Code below:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
ts_index = pd.period_range(start="20060429", periods=1000).strftime("%Y%m%d")
fig = plt.figure(1)
ax = plt.subplot(1, 1, 1)
tick_spacing = 60
for i in range(5):
plt.plot(ts_index, 1 + i * 0.01 * np.arange(0, 1000), label="group %d"%i)
plt.legend(loc='best')
plt.title(r'net value curves')
xticks = ax.get_xticks()
xlabels = ax.get_xticklabels()
ax.set_xticks(xticks[::tick_spacing])
ax.set_xticklabels(xlabels[::tick_spacing])
plt.xticks(rotation="vertical")
plt.xlabel(r'date')
plt.ylabel('net value')
plt.grid(True)
plt.show()
fig.savefig(r".\net_value_curves.png", )
fig.clf()
I'm running this piece of code in PyCharm Community Edition 2017.2.2 with a Python 3.6 kernel. Now comes the funny thing: whenever I ran the code in the normal "run" mode (i.e. just hit the execution button and let the code run "freely" till interruption or termination), then the figure I got would always miss xticklabels:
However, if I ran the code in "debug" mode and ran it step by step then I would get an expected figure with complete xticklabels:
This is really weird. Anyway, I just hope to find a way that can ensure me getting the desired output (the second figure) in the normal "run" mode. How can I modify my current code to achieve this?
Thanks in advance!
Your x axis data are strings. Hence you will get one tick per data point. This is probably not what you want. Instead use the dates to plot. Because you are using pandas, this is easily converted,
dates = pd.to_datetime(ts_index, format="%Y%m%d")
You may then get rid of your manual xtick locating and formatting, because matplotlib will automatically choose some nice tick locations for you.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
ts_index = pd.period_range(start="20060429", periods=1000).strftime("%Y%m%d")
dates = pd.to_datetime(ts_index, format="%Y%m%d")
fig, ax = plt.subplots()
for i in range(5):
plt.plot(dates, 1 + i * 0.01 * np.arange(0, 1000), label="group %d"%i)
plt.legend(loc='best')
plt.title(r'net value curves')
plt.xticks(rotation="vertical")
plt.xlabel(r'date')
plt.ylabel('net value')
plt.grid(True)
plt.show()
However in case you do want to have some manual control over the locations and formats you may use matplotlib.dates locators and formatters.
# tick every 3 months
plt.gca().xaxis.set_major_locator(mdates.MonthLocator((1,4,7,10)))
# format as "%Y%m%d"
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y%m%d"))
In general, the Axis object computes and places ticks using a Locator object. Locators and Formatters are meant to be easily replaceable, with appropriate methods of Axis. The default Locator does not seem to be doing the trick for you so you can replace it with anything you want using axes.xaxis.set_major_locator. This problem is not complicated enough to write your own, so I would suggest that MaxNLocator fits your needs fairly well. Your example seems to work well with nbins=16 (which is what you have in the picture, since there are 17 ticks.
You need to add an import:
from matplotlib.ticker import MaxNLocator
You need to replace the block
xticks = ax.get_xticks()
xlabels = ax.get_xticklabels()
ax.set_xticks(xticks[::tick_spacing])
ax.set_xticklabels(xlabels[::tick_spacing])
with
ax.xaxis.set_major_locator(MaxNLocator(nbins=16))
or just
ax.xaxis.set_major_locator(MaxNLocator(16))
You may want to play around with the other arguments (all of which have to be keywords, except nbins). Pay especial attention to integer.
Note that for the Locator and Formatter APIs we work with an Axis object, not Axes. Axes is the whole plot, while Axis is the thing with the spines on it. Axes usually contains two Axis objects and all the other stuff in your plot.
You can set the visibility of the xticks-labels to False
for label in plt.gca().xaxis.get_ticklabels()[::N]:
label.set_visible(False)
This will set every Nth label invisible.
I was trying to plot a time series data figure using matplotbib, the problem is that there are too many observations, therefore the labels have overlap and don't fit well within a sized figure.
I am thinking of three solutions, one is to shrink the label size of observations, one is to change the text into vertical order or skewed manner, last is only to specify the first and last a few observations with dots between them. The code is to demonstrate my point.
I wonder anyone can help? Thanks
from datetime import date
import numpy as np
from pandas import *
import matplotlib.pyplot as plt
N = 100
data = np.array(np.random.randn(N))
time_index = date_range(date.today(), periods = len(data))
plt.plot(time_index, data)
For your simple plot, you could do
plt.xticks(rotation=90).
Alternatively, you could specify what ticks you wanted to display with
plt.xticks(<certain range of values>)
plt.xticklabels(<labels for those values>)
Edit:
Personally, I would change to the object-oriented way of pyplot.
f = plt.figure()
ax = f.add_subplot(111)
ax.plot(<stuff>)
ax.tick_params(axis='x', labelsize='8')
plt.setp( ax.xaxis.get_majorticklabels(), rotation=90 )
# OR
xlabels = ax.get_xticklabels()
for label in xlabels:
label.set_rotation(90)
plt.show()
I am developing some code to produce an arbitrary number of 2D plots (maps and simple contour plots) on a figure. The matplotlib subplots routine works great for this. In the simplified example below, everything works as it should. However, in my real application - which uses the exact same commands for subplots, contourf and colorbar, only that these are dispersed across several routines - the labels on the colorbars are not showing up (the color patches seem to be ok though). Even after hours of reading documentation and searching the web, I don't even have a clue where I could start looking for what the problem is. If I have my colorbar instance (cbar), I should be able to find out if the ticklabel position makes sense, if the ticklabels are set to visible, if my font settings make sense, etc.... But how do I actually check these properties? Has anyone encountered similar problems already? (and even better: found a solution?) Oh yes: if I manually create a new figure and axes in the actual plotting routine (where the contourf command is issued), then it will work again. But that means losing all control over the figure layout etc. Could it be that I am not passing my axes instance correctly? Here is what I do:
fig, ax = plt.subplots(nrows, ncols)
row, col = getCurrent(...)
plotMap(x, y, data, ax=ax[row,col], ...)
Then, inside plotMap:
c = ax.contourf(x, y, data, ...)
ax.figure.colorbar(c, ax=ax, orientation="horizontal", shrink=0.8)
As said above, the example below with simplified plots and artificial data works fine:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0.,360.,5.)*np.pi/180.
y = np.arange(0.,360.,5.)*np.pi/180.
data = np.zeros((y.size, x.size))
for i in range(x.size):
data[:,i] = np.sin(x[i]**2*y**2)
fig, ax = plt.subplots(2,1)
contour = ax[0].contourf(x, y, data)
cbar = ax[0].figure.colorbar(contour, ax=ax[0], orientation='horizontal', shrink=0.8)
contour = ax[1].contourf(x, y, data, levels=[0.01,0.05,0.1,0.05])
cbar = ax[1].figure.colorbar(contour, ax=ax[1], orientation='horizontal', shrink=0.8)
plt.show()
Thanks for any help!
Addition after some further poking around:
for t in cbar.ax.get_xticklabels():
print t.get_position(), t.get_text(), t.get_visible()
shows me the correct text and visible=True, but all positions are (0.,0.). Could this be a problem?
BTW: axis labels are also missing sometimes... and I am using matplotlib version 1.1.1 with python 2.7.3 on windows.
OK - I could track it down: matplotlib is working as it should!
The error was embedded in a utility routine that adds some finishing touches to each page (=figure) once the given number of plot panels has been produced. In this routine I wanted to hide empty plot panels (i.e. on the last page) and I did this with
ax = fig.axes
for i in range(axCurrent, len(ax)):
ax[i].set_axis_off()
However, axCurrent was already reset to zero when the program entered this routine for any page but the last, hence the axes were switched off for all axes in figure. Adding
if axCurrent > 0:
before the for i... solves the problem.
Sorry if I stole anyone's time. Thanks anyway to everyone who was considering to help!