Six subplots with the same number of xticklabels in matplotlib - python

I am really struggling with matplotlib, escpecially with the axis settings. My goal is to set up 6 subplots in one figure, which all display different datasets but have the same amount of ticklabels.
The relevant part of my sourcecode looks like:
graph4.py:
# Import Matolotlib Modules #
import matplotlib as mpl
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
from matplotlib import ticker
import matplotlib.pyplot as plt
mpl.rcParams['font.sans-serif']='Arial' #set font to arial
# Import GTK Modules #
import gtk
#Import System Modules #
import sys
# Import Numpy Modules #
from numpy import genfromtxt
import numpy
# Import Own Modules #
import mysubplot as mysp
class graph4():
weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']
def __init__(self, graphview):
#create new Figure
self.figure = Figure(figsize=(100,100), dpi=75)
#create six subplots within self.figure
self.subplot = []
for j in range(6):
self.subplot.append(self.figure.add_subplot(321 + j))
self.__conf_subplots__() #configure title, xlabel, ylabel and grid of all subplots
#to make it look better
self.figure.subplots_adjust(left=0.125, bottom=0.1, right=0.9, top=0.96, wspace=0.2, hspace=0.6)
#Matplotlib <-> GTK
self.canvas = FigureCanvas(self.figure) # a gtk.DrawingArea
self.canvas.set_flags(gtk.HAS_FOCUS|gtk.CAN_FOCUS)
self.canvas.grab_focus()
self.canvas.show()
graphview.pack_start(self.canvas, True, True)
#add labels and grid to all subplots
def __conf_subplots__(self):
index = 0
for i in self.subplot:
mysp.conf_subplot(i, 'Zeit', 'Menge', graph4.weekdays[index], True)
i.plot([], [], 'bo') #empty plot
index +=1
def plot(self, filename_list):
index = 0
for filename in filename_list:
data = genfromtxt(filename, delimiter=',') #load data from filename
if data.size != 0: #only if file isn't empty
if index <= len(self.subplot): #plot every file on a different subplot
mysp.plot(self.subplot[index],data[0:, 1], data[0:, 0])
index +=1
self.canvas.draw()
def clear_plot(self):
#clear axis of all subplots
for i in self.subplot:
i.cla()
self.__conf_subplots__()
mysubplot.py: (helper module)
# Import Matplotlib Modules
from matplotlib.axes import Subplot
import matplotlib.dates as md
import matplotlib.pyplot as plt
# Import Own Modules #
import mytime as myt
# Import Numpy Modules #
import numpy as np
def conf_subplot(subplot, xlabel, ylabel, title, grid):
if(xlabel != None):
subplot.set_xlabel(xlabel)
if(ylabel != None):
subplot.set_ylabel(ylabel)
if(title != None):
subplot.set_title(title)
subplot.grid(grid)
#rotate xaxis labels
plt.setp(subplot.get_xticklabels(), rotation=30, fontsize=12)
#display date on xaxis
subplot.xaxis.set_major_formatter(md.DateFormatter('%H:%M:%S'))
subplot.xaxis_date()
def plot(subplot, x, y):
subplot.plot(x, y, 'bo')
I think the best way to explain what goes wrong is with the use of screenshots. After I start my application, everything looks good:
If I double click a 'Week'-entry on the left, the method clear_plot() in graph4.py is called to reset all subplots. Then a list of filenames is passed to the method plot() in graph4.py. The method plot() opens each file and plots each dataset on a different subplot. So after I double click a entry, it looks like:
As you can see, each subplot has a different number of xtick labels, which looks pretty ugly to me. Therefore, I am looking for a solution to improve this. My first approach was to set the ticklabels manually with xaxis.set_ticklabels(), so that each subplot has the same number of ticklabels. However, as strange as it sounds, this only works on some datasets and I really don't know why. On some datasets, everything works fine and on other datasets, matplotlib is basically doing what it wants and displays xaxis labels that I didn't specify. I also tried FixedLocator(), but I got the same result. On some datasets it is working and on others, matplotlib is using a different number of xtick labels.
What am I doing wrong?
Edit:
As #sgpc suggested, I tried to use pyplot. My sourcecode now looks like this:
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
import matplotlib.dates as md
mpl.rcParams['font.sans-serif']='Arial' #set font to arial
import gtk
import sys
# Import Numpy Modules #
from numpy import genfromtxt
import numpy
# Import Own Modules #
import mysubplot as mysp
class graph2():
weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']
def __init__(self, graphview):
self.figure, temp = plt.subplots(ncols=2, nrows=3, sharex = True)
#2d array -> list
self.axes = [ y for x in temp for y in x]
#axis: date
for i in self.axes:
i.xaxis.set_major_formatter(md.DateFormatter('%H:%M:%S'))
i.xaxis_date()
#make space and rotate xtick labels
self.figure.autofmt_xdate()
#Matplotlib <-> GTK
self.canvas = FigureCanvas(self.figure) # a gtk.DrawingArea
self.canvas.set_flags(gtk.HAS_FOCUS|gtk.CAN_FOCUS)
self.canvas.grab_focus()
self.canvas.show()
graphview.pack_start(self.canvas, True, True)
def plot(self, filename_list):
index = 0
for filename in filename_list:
data = genfromtxt(filename, delimiter=',') #get dataset
if data.size != 0: #only if file isn't empty
if index < len(self.axes): #print each dataset on a different subplot
self.axes[index].plot(data[0:, 1], data[0:, 0], 'bo')
index +=1
self.canvas.draw()
#not yet implemented
def clear_plot(self):
pass
If I plot some datasets, I get the following output:
http://i.imgur.com/3ngYTNr.png (sorry, I still don't have enough reputation to embedd pictures)
Moreover, I am not really sure if sharing the x-axis is a really good idea, because it is possible that the x-values differ in every subplot (for example: in the first subplot, the x-values ranges from 8:00am - 11:00am and in the second subplot the x-values ranges from 7:00pm - 9:00pm).
If I get rid of sharex = True, I get the following output:
http://i.imgur.com/rxHeSyJ.png (sorry, I still don't have enough reputation to embedd pictures)
As you can see, the output now looks better. However now, the labels on the x-axes are not updated. I assume that is because the last suplots are empty.
My next attempt was to use an axis for each subplot. Therefore, I made this changes:
for i in self.axes:
plt.setp(i.get_xticklabels(), visible=True, rotation = 30) #<-- I added this line...
i.xaxis.set_major_formatter(md.DateFormatter('%H:%M:%S'))
i.xaxis_date()
#self.figure.autofmt_xdate() #<--changed this line
self.figure.subplots_adjust(left=0.125, bottom=0.1, right=0.9, top=0.96, wspace=0.2, hspace=0.6) #<-- and added this line
Now I get the following output:
i.imgur.com/TmA1goE.png (sorry, I still don't have enough reputation to embedd pictures)
So with this attempt, I am basically struggling with the same problem as with Figure() and add_subplot().
I really don't know, what else I could try to make it work...

I would strongly recommend you to use pyplot.subplots() with sharex=True:
fig, axes = subplots(ncols=2, nrows=3, sharex= True)
Then you access each axes using:
ax = axes[i,j]
And you can plot doing:
ax.plot(...)
To control the number of ticks for each AxesSubplot you can use:
ax.locator_params(axis='x', nbins=6)
OBS: axis can be 'x', 'y' or 'both'

Related

misplaced subplots using Venn diagrams and GridSpec [python / matplotlib]

I'm having troubles creating a grid layout plot using two different venn packages, the first one is the matplot-venn and the other is the venn package to intersect more than 3 subsets. The problem with the venn package is that its creating a new figure and axis, therefore I'm creating all plots in a function and then add them to my composite figure. The initial grid is placed properly (img1), also when adding the subplots with the corresponding gridSpec the coordinates are correct, but when plotting the layout is completely off (img2).first of all it seems only half of the figure x axis is used and the 4 small venns should be roughly the same size and rows as the two bigger ones on the left.
img1 correct grid layout
img2 wrong layout
I tried several other things e.g. adjusting parameters such as autoscaling, squeeze, clipping or subplot specs etc. , unfortunately all with the same result.
I'm working on a server and connecting via ssh plus xforwarding, either using a mac (quartz) or windows (xming) and the issue is present on both machines.
I'm new to python plotting so any help or suggestions are highly appreciated
thanks a lot
import os
import re
import copy
import pandas as pd
import venn # for 4 way venns, non proportional
import matplotlib.pyplot as plt
from matplotlib_venn import venn3
from matplotlib.gridspec import GridSpec
from matplotlib.patches import Rectangle
# for ssh connections through windows using a non-interactive backend
# import matplotlib
# matplotlib.use('Agg')
def extractORFName(orfList):
return set([re.sub("(.*)_[0-9]*aa.*","\\1",x) for x in orfList])
def plotValidatedOverlap(orfList,nameList):
labels = venn.get_labels(orfList, fill=['number'])
figX, axVenn = venn.venn5(labels, names=nameList,fontsize=8)
axVenn.get_legend().remove()
v_human = plotVenn3(orfList[0:3],nameList[0:3])
v_mouse = plotVenn3(orfList[1:4],nameList[1:4])
plt.close(figX)
return (axVenn, v_human, v_mouse)
def plotVenn3(orfList,nameList):
fig ,ax = plt.subplots()
out = venn3(orfList,nameList,normalize_to=0.75)
for text in out.set_labels:
text.set_fontsize(8)
for text in out.subset_labels:
if text is not None:
text.set_fontsize(6)
plt.close(fig)
return ax
def copyElement(axList,gs,fig):
for idx in range(0,len(axList)):
print("\n\n### processing idx: %s" % idx)
pos = gs[idx].get_position(fig)
ax = copy.copy(axList[idx])
ax.figure = fig
ax.set_position(pos)
ax.set_subplotspec(gs[idx])
fig.add_subplot(ax,aspect='equal',autoscale_on=True)
fig.axes[idx].set_clip_on(False)
print("axes positions:\t%s" % str(fig.axes[idx].get_position()))
print("gs position:'t%s" % str(pos))
#fig1.canvas.draw()
#input("next axes")
#### data input to create orfList (list of sets as subsets) and nameList (labels of subsets)
fig1, ax = plt.subplots(dpi=100, figsize=(18,8),num="venn")#, squeeze=False, constrained_layout=True)
fig1.axes[0].remove()
gs = GridSpec(nrows=2, ncols=3, width_ratios=[2,1,1], figure=fig1)
figsToPlot = sum([ list(plotValidatedOverlap(orfList[type],nameList)) for type in orfList ],[])
fig1.clear()
fig1.suptitle(cellType + "_" + method + "_overlap")
fig1.canvas.draw()
copyElement(figsToPlot,gs,fig1)
fig1.tight_layout()
fig1.canvas.draw()

Matplotlib graphics problems in python

I have the following graphic generated with the following code
I want to correct the x-axis display to make the date more readable.
I would also like to be able to enlarge the graph
My code is :
import requests
import urllib.parse
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
def get_api_call(ids, **kwargs):
API_BASE_URL = "https://apis.datos.gob.ar/series/api/"
kwargs["ids"] = ",".join(ids)
return "{}{}?{}".format(API_BASE_URL, "series", urllib.parse.urlencode(kwargs))
df = pd.read_csv(get_api_call(
["168.1_T_CAMBIOR_D_0_0_26", "101.1_I2NG_2016_M_22",
"116.3_TCRMA_0_M_36", "143.3_NO_PR_2004_A_21", "11.3_VMATC_2004_M_12"],
format="csv", start_date=2018
))
time = df.indice_tiempo
construccion=df.construccion
emae = df.emae_original
time = pd.to_datetime(time)
list = d = {'date':time,'const':construccion,'EMAE':emae}
dataset = pd.DataFrame(list)
plt.plot( 'date', 'EMAE', data=dataset, marker='o', markerfacecolor='blue', markersize=12, color='skyblue', linewidth=4)
plt.plot( 'date', 'const', data=dataset, marker='', color='olive', linewidth=2)
plt.legend()
To make the x-tick labels more readable, try rotating them. So use, for example, a 90 degree rotation.
plt.xticks(rotation=90)
To enlarge the size, you can define your own size using the following in the beginning for instance
fig, ax = plt.subplots(figsize=(10, 8))
I am fairly sure that this can be done by using the window itself of Matplotlib. If you have the latest version you can enlarge on a section of the graph by clicking the zoom button in the bottom left. To get the x-tick labels to be more readable you can simply click the expand button in the top right or use Sheldore's solution.

matplotlib event.xdata out of timeries range

Having an issue using matplotlib event.xdata when plotting pandas.Timeseries, I tried to reproduce the answer proposed in a very related question, but get a very strange behavior.
Here's the code, adapted to python3 and with a little more stuff in the on_click() function:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
def on_click(event):
if event.inaxes is not None:
# provide raw and converted x data
print(f"{event.xdata} --> {mdates.num2date(event.xdata)}")
# add a vertical line at clicked location
line = ax.axvline(x=event.xdata)
plt.draw()
t = pd.date_range('2015-11-01', '2016-01-06', freq='H')
y = np.random.normal(0, 1, t.size).cumsum()
df = pd.DataFrame({'Y':y}, index=t)
fig, ax = plt.subplots()
line = None
df.plot(ax=ax)
fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()
If I launch this, I get the following diagram, with expected date range between Nov. 2015 and Jan. 2016, as is the cursor position information provided in the footer of the window (here 2015-11-01 10:00), and correct location of the vertical lines:
However, the command-line output is as follows:
C:\Users\me\Documents\code\>python matplotlib_even.xdate_num2date.py
402189.6454115977 --> 1102-02-27 15:29:23.562039+00:00
402907.10400704964 --> 1104-02-15 02:29:46.209088+00:00
Those event.xdata values are clearly out of both input data range and x axis data range, and are unusable for later use (like, try to find the closest y value in the serie).
So, does anyone know how I can get a correct xdata?
Something must have changed in the way matplotlib/pandas handles datetime info between the answer to the related question you linked and now. I cannot comment on why, but I found a solution to your problem.
I went digging through the code that shows the coordinates in the bottom left of the status bar, and I found that when you're plotting a timeseries, pandas patches the functions that prints this info and replaces it with this one.
From there, you can see that you need to convert the float value to a Period object.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def on_click(event):
print(pd.Period(ordinal=int(event.xdata), freq='H'))
t = pd.date_range('2015-11-01', '2016-01-06', freq='H')
y = np.random.normal(0, 1, t.size).cumsum()
df = pd.DataFrame({'Y': y}, index=t)
fig, ax = plt.subplots()
df.plot(ax=ax)
fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()

Python - Matplotlib - Setting X axis range - Plotting Packets Per Second

I'm working on a script that plots a pps count versus time from a csv file. Everything works up to this point however I can't seem to figure out how to change the interval at which the ticks/tick-labels occur at on the X-axis, I want there to be 60 timestamps/tick instead of the default. Here's where I'm at:
import matplotlib
matplotlib.use('Agg')
from matplotlib.mlab import csv2rec
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
from pylab import *
data = csv2rec('tpm_counter.log', names=['packets', 'time']) # reads in the data from the csv as column 1 = tweets column 2 = time
rcParams['figure.figsize'] = 12, 4 # this sets the ddimensions of the graph to be made
rcParams['font.size'] = 8
fig = plt.figure()
plt.plot(data['time'], data['packets']) # this sets the fields to be graphed
plt.xlabel("Time(minutes)") # this sets the x label
plt.ylabel("Packets") # this sets the y label
plt.title("Packets Capture Log: Packets Per Minute") # this sets the title
#plt.xticks(range(60)) --- nothing shows on the graph if I use this
fig.autofmt_xdate(bottom=0.2, rotation=90, ha='left')
plt.savefig('tpm.png') # this sets the output file name
I've tried plt.xticks(range(60)) but when the plot generates, it has nothing on it.
bmu's answer above works. But it might be helpful to others to see a more general way of rescaling the xticks and xlabels in a plot. I have generated some example data instead of using a csv file.
import matplotlib
import matplotlib.pyplot as plt
from pylab import *
time=range(5000) #just as an example
data=range(5000) # just as an example
fig = plt.figure()
plt.plot(time,data) # this sets the fields to be graphed
plt.xlabel("Every 60th point") # this sets the x label
plt.ylabel("Data") # this sets the y label
plt.title("Rescaling axes") # this sets the title
#Slice the data into every 60th point. We want ticks at these points
tickpos=data[::60]
#Now create a list of labels for each point...
ticklabels=[]
for point in tickpos:
ticklabels.append(str(point/60))
plt.xticks(tickpos,ticklabels) # set the xtick positions and labels
plt.savefig('tpm.png')
Have a look at the date demo.
You can use the HourLocator or the MinuteLocator together with an adapted DateFormatter.
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot_date(data['time'], data['packets'])
hours = mdates.HourLocator()
fmt = mdates.DateFormatter('%H:%M')
ax.xaxis.set_major_locator(hours)
ax.xaxis.set_major_formatter(fmt)

Using Colormaps to set color of line in matplotlib

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

Categories