plotting multiple time series with different scales - python

I'm trying to plot 4 different dataframes over time to highlight possible relations between them.
I've met several difficulties:
different scales
same values overlap each other (IORR and IOER curves)
curves have big "points" making them unreadable
can't shift bar plots x values with df.index + 0.1 as I get an error
regarding the point 2, trying to shift the bars between the df IORR and IOER this way:
p1 = ax1.bar(df_ioer.index + 0.1, df_ioer.Value, ls='dashed', label='IOER', color='g')
ax1.xaxis_date()
I get this error:
TypeError: unsupported operand type(s) for +: 'DatetimeIndex' and 'float'
overall it's getting a little too much.
Could someone give a few pointers around this issue to get an intuitive representation of the data?
here is the code:
import quandl
from cycler import cycler
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt
quandl.ApiConfig.api_key = "Get Free Key From Quandl.com"
df_dff = quandl.get("FRED/DFF")
df_iorr = quandl.get("FRED/IORR")
df_ioer = quandl.get("FRED/IOER")
df_gdp = quandl.get("FRED/GDP")
df_dff = df_dff[df_dff.index >= df_iorr.index.min()]
df_iorr = df_iorr[df_iorr.index >= df_iorr.index.min()]
df_ioer = df_ioer[df_ioer.index >= df_iorr.index.min()]
df_gdp = df_gdp[df_gdp.index >= df_iorr.index.min()]
# https://matplotlib.org/gallery/ticks_and_spines/multiple_yaxis_with_spines.html
plt.rc('axes', prop_cycle=(cycler('color', ['r', 'c', 'm', 'y', 'k', 'b', 'g', 'r', 'c', 'm'])))
def make_patch_spines_invisible(ax):
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
fig, ax0 = plt.subplots()
#p0, = ax0.plot_date(df_iorr.index, df_iorr.Value, ls='dashed', tz=None, xdate=True, ydate=False, label='IORR', color='r')
#ax0.yaxis.label.set_color(p0.get_color())
p0 = ax0.bar(df_iorr.index, df_iorr.Value, ls='dashed', label='IORR', color='r')
ax0.xaxis_date( tz=None)
ax1 = ax0.twinx()
#p1, = ax0.plot_date(df_ioer.index, df_ioer.Value, ls='dashed', tz=None, xdate=True, ydate=False, label='IOER', color='g')
#ax1.yaxis.label.set_color(p1.get_color())
p1 = ax1.bar(df_ioer.index, df_ioer.Value, ls='dashed', label='IOER', color='g')
ax1.xaxis_date( tz=None)
ax2 = ax0.twinx()
p2, = ax0.plot_date(df_dff.index, df_dff.Value, ls='solid', tz=None, xdate=True, ydate=False, label='DFF', color='b')
ax2.spines["right"].set_position(("axes", 1.2))
make_patch_spines_invisible(ax2)
ax2.spines["right"].set_visible(True)
ax3 = ax0.twinx()
p3, = ax3.plot_date(df_gdp.index, df_gdp.Value, ls='solid', tz=None, xdate=True, ydate=False, label='GDP', color='y')
lines = [p0, p1, p2, p3]
ax0.legend(lines, [l.get_label() for l in lines])
plt.show()
result looks like this and is nowhere good enough.
Any help greatly appreciated!

Related

Line chart to surface chart

[UPDATE: Sorry for not providing the piece where the author of the codes create example data. I have updated the codes]
I found an example of a 3D mesh line chart that satisfied what I need (colouring change with level on z dimension). However, instead of line, I want surface plot. How can I change the codes to have the 3d surface plot?
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from matplotlib.cm import get_cmap
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.font_manager import FontProperties
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap
from mpl_toolkits.mplot3d.art3d import Line3DCollection
index_returns = np.random.normal(loc=1e-4, scale=5e-3, size=(783, 9))
index_returns = np.vstack((np.zeros(shape=(1, 9)) + 100, index_returns))
index_prices = np.cumprod(1 + index_returns, axis=0)
window = 261
df = np.zeros(shape=(index_prices.shape[0]-window, 9))
for i in range(window, index_prices.shape[0], 1):
df[i-window] = (index_prices[i]/index_prices[i-window]) - 1
index = pd.date_range('2019-01-01', periods=index_prices.shape[0]-window, freq='B')
columns = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
df = pd.DataFrame(df, index=index, columns=columns)
# create the figure
fig = plt.figure(figsize=(14.4, 9))
ax = fig.add_subplot(111, projection='3d')
fig.patch.set_alpha(1)
# get the cmap to use
cmap = get_cmap('RdYlGn')
# get the slice based on data frame
current_slice = df.values[:261, :]
index_names = df.columns
index_dates = df.index
# list holding the lines
lines = []
# for each index...
for i in range(current_slice.shape[1]):
# get the coordinates
x = np.array(np.arange(current_slice.shape[0]))
y = np.tile(i, current_slice.shape[0])
z = np.array(current_slice[:, i])
# crete points and segments to color
points = np.array([x, y, z]).T.reshape(-1, 1, 3)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
# Create a continuous norm to map from data points to colors
norm = plt.Normalize(-0.19, 0.19)
lc = Line3DCollection(segments, cmap=cmap, norm=norm, zorder=current_slice.shape[1]-i)
# Set the values used for colormapping
lc.set_array(z)
lc.set_linewidth(2)
lc.set_color(cmap(z[-1] * 2.5 + 0.5))
lc.set_label(index_names[i])
lines.append(ax.add_collection(lc))
# add the grids
ax.legend(loc='center right', bbox_to_anchor=(1.1, 0.46), fancybox=True, facecolor=(.95,.95,.95,1), framealpha=1, shadow=False, frameon=True, ncol=1, columnspacing=0, prop={'family': 'DejaVu Sans Mono'})
ax.set_zlabel('Rolling Equity 1Y', labelpad=10)
ax.set_zlim(-0.39, 0.39)
ax.set_zticklabels([' '* 3 + '{:.0%}'.format(val) for val in ax.get_zticks()], fontdict={'verticalalignment': 'center', 'horizontalalignment': 'center'})
ax.set_xlabel('Date', labelpad=30)
ax.set_xlim(0, current_slice.shape[0]-1)
ax.set_xticklabels([index_dates[int(val)].strftime('%m/%y') for val in ax.get_xticks()[:-1]] + [''], rotation=0, fontdict={'verticalalignment': 'top', 'horizontalalignment': 'center'})
ax.set_yticks(np.arange(current_slice.shape[1]))
ax.set_yticklabels([index_names[i] for i in range(current_slice.shape[1])], rotation=-15, fontdict={'verticalalignment': 'center', 'horizontalalignment': 'left'})
# show the plot
plt.show()

How to create a 3D graph with filled-below curves and position the y-ticks?

I am making my first 3D graph in Python on an Anaconda Jupyter Notebook. The idea is to obtain a graph with a format similar to the following:
The code I made is as follows:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = Axes3D(fig)
def func(x, pos): # formatter function takes tick label and tick position
s = str(x)
ind = s.index('.')
return s[:ind] + ',' + s[ind+1:] # change dot to comma
x_format = tkr.FuncFormatter(func)
ax.xaxis.set_major_formatter(x_format)
ax.yaxis.set_major_formatter(x_format)
df = pd.read_excel('EDS 7.xlsx', header=None, usecols=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], names=['A', 'B', 'C', 'D','E','F','G','H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P'])
plt.rcParams["figure.figsize"] = [14.5,10]
nomes = ['Triângulo de MoSe$_2$','Losango branco','Losango cinzento','Fundo']
ax.set_yticks(range(0,4))
ax.set_yticklabels(nomes)
ax.tick_params(axis='z', pad=10)
ax.tick_params(axis='y', pad=20)
# put 0s on the y-axis, and put the y axis on the z-axis
ax.plot(xs=df['A'], ys=df['B'], zs=df['C'], zdir='z', label='ys=0, zdir=z', color='blue', linewidth=3)
ax.plot(xs=df['D'], ys=df['E'], zs=df['F'], zdir='z', label='ys=0, zdir=z', color='red', linewidth=3)
ax.plot(xs=df['G'], ys=df['H'], zs=df['I'], zdir='z', label='ys=0, zdir=z', color='green', linewidth=3)
ax.plot(xs=df['J'], ys=df['K'], zs=df['L'], zdir='z', label='ys=0, zdir=z', color='orange', linewidth=3)
y=df['M'];
plt.xlim([0.0, 4.0])
#plt.ylim([0.0, 4.0])
ax.set_zlim(0,1400)
plt.rc('xtick', labelsize=16)
plt.rc('ytick', labelsize=16)
#plt.xticks(np.arange(0.0,1.4,0.1).round(decimals=1))
#plt.yticks(np.arange(-0.8,1.3,0.2).round(decimals=1))
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.w_zaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.xaxis._axinfo["grid"]['linestyle'] = '--'
ax.xaxis._axinfo["grid"]['color'] = 'silver'
ax.yaxis._axinfo["grid"]['linestyle'] = '--'
ax.yaxis._axinfo["grid"]['color'] = 'silver'
ax.zaxis._axinfo["grid"]['linestyle'] = '--'
ax.zaxis._axinfo["grid"]['color'] = 'silver'
ax.set_xlabel('Energia (keV)', fontsize=20, labelpad=18)
ax.set_zlabel('Contagens', fontsize=20, labelpad=18)
#plt.show()
plt.savefig('output.png', dpi=500, bbox_inches='tight')
Excel file:
The graphic I got is this:
I am having two problems that I am unable to solve:
The underside of the lines is not filled with color and I would like them to be opaque.
In the yy axis, the strings are too far to the left and for example the string "Triângulo de MoSe2" of the yy axis is to the left of the number 4.0 of the xx axis. I would like the y-axis strings to be more centered.
How can I adjust the code for the graph to have these two characteristics that I lack?
Here is an example to create something similar to the desired plot. Some toy data are used to create 4 curves.
To fill the area below the curves, the approach from this tutorial is used. For the y tick labels, it seems ax.set_yticklabels(..., ha='left') together with ax.tick_params(axis='y', pad=0) get quite close to the desired result.
To make the polygons fully opaque, set the opaqueness alpha in PolyCollection(...) to a value closer to 1. Usually a small bit of transparency gives a better feeling of being a 3D plot. You can leave out the call to ax.plot(...) if the thicker "border" isn't needed.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.collections import PolyCollection
def polygon_under_graph(xlist, ylist):
return [(xlist[0], 0.), *zip(xlist, ylist), (xlist[-1], 0.)]
x_cols = {col: np.linspace(0, 4, 50) for col in [*'ADGJ']}
z_cols = {col: 1400 ** np.random.rand(50) for col in [*'CFIL']}
df = pd.DataFrame({**x_cols, **z_cols})
fig = plt.figure()
ax = Axes3D(fig)
plt.rcParams["figure.figsize"] = [14.5, 10]
nomes = ['Triângulo de MoSe$_2$', 'Losango branco', 'Losango cinzento', 'Fundo']
ax.set_yticks(range(0, 4))
ax.set_yticklabels(nomes, ha='left')
ax.tick_params(axis='z', pad=10)
ax.tick_params(axis='y', pad=0)
color_list = ['cornflowerblue', 'crimson', 'limegreen', 'orange']
verts = []
ys = [3, 2, 1, 0]
for x_col, z_col, y, color in zip(['A', 'D', 'G', 'J'], ['C', 'F', 'I', 'L'], ys, color_list):
xs = df[x_col].to_numpy()
zs = df[z_col].to_numpy()
ax.plot(xs=xs, ys=np.repeat(y, len(xs)), zs=zs, zdir='z', color=color, linewidth=3)
verts.append(polygon_under_graph(xs, zs))
poly = PolyCollection(verts, facecolors=color_list, alpha=.8)
ax.add_collection3d(poly, zs=ys, zdir='y')
plt.show()
About having the outlines of the 3 panes in black, some experimenting with 3D figures from Matplotlib visibility of pane edge leads to the following. It is unclear to me why that hack works (an other approaches don't).
def lims(mplotlims):
scale = 1.021
offset = (mplotlims[1] - mplotlims[0]) * scale
return mplotlims[1] - offset, mplotlims[0] + offset
xlims, ylims, zlims = lims(ax.get_xlim()), lims(ax.get_ylim()), lims(ax.get_zlim())
i = np.array([xlims[0], ylims[0], zlims[0]])
f = np.array([xlims[0], ylims[0], zlims[1]])
p = art3d.Poly3DCollection(np.array([[i, f]]))
p.set_color('black')
ax.add_collection3d(p)
ax.xaxis.pane.set_edgecolor('#000000FF')
ax.yaxis.pane.set_edgecolor('#000000FF')
ax.zaxis.pane.set_edgecolor('#000000FF')

How to assign color to error bar caps? [Matplotlib]

I am new at Matplotlib and would like to assign colors to error bar caps...in my data (attached) the mean values are 'numbers' and the SD ('error') is in the column 'sd'. I grouped data by 'strain' (4 categories; mc, mut1, etc.). Colors are 'strains' (lines). The code below works BUT When I use "capsize" to add caps it throws an error...
I want the caps to have the same color as lines (from color vector "c"), any way? Thanks!
The file is https://anonfiles.com/d8A7m4F5o0/mutdp_csv
muts = pd.read_csv('mutdp.csv')
#SUBSET
# Select rows (i.e. 1 to 28)
gr=muts[1:28]
fig, ax = plt.subplots(figsize=(12,9.9))
c=['b','y','r','g']
#Data GR ---------------------------------------------------------------------------------------------
grstrain=gr.groupby(['time','strain']).mean()['numbers'].unstack()
grstrain.plot.line(ax=ax, style=['-o','-o','-o','-o'],color=c, ls = '--', linewidth=2.7)
# Error (-----HERE is where "capsize" causes the error----)
ax.errorbar(gr.time, gr.numbers, yerr=gr.sd, ls='', color=[i for i in c for _i in range(7)], capsize=3, capthick=3)
#(SCALE)
plt.yscale('log')
plt.ylim(0.04, 3)
#SAVE FIG!
plt.show()
As ax.errorbar only accepts one fixed color, it could be called in a loop, once for each color. The following code creates some random data to show how the loop could be written:
from matplotlib import pyplot as plt
import matplotlib
import numpy as np
import pandas as pd
gr = pd.DataFrame({'time': np.tile(range(0, 14, 2), 4),
'strain': np.repeat(['mc', 'mut1', 'mut2', 'mut3'], 7),
'numbers': 0.1 + np.random.uniform(-0.01, 0.06, 28).cumsum(),
'sd': np.random.uniform(0.01, 0.05, 28)})
fig, ax = plt.subplots(figsize=(12, 9.9))
colors = ['b', 'y', 'r', 'g']
grstrain = gr.groupby(['time', 'strain']).mean()['numbers'].unstack()
grstrain.plot.line(ax=ax, style=['-o', '-o', '-o', '-o'], color=colors, ls='--', linewidth=2.7)
for strain, color in zip(np.unique(gr.strain), colors):
grs = gr[gr.strain == strain]
ax.errorbar(grs.time, grs.numbers, yerr=grs.sd, ls='', color=color, capsize=3, capthick=3)
plt.yscale('log')
plt.ylim(0.04, 3)
plt.show()

Plot a line between prediction and ground_truth point in matplotlib

I have two dataframes, ground_truth and prediction (Both are pandas series). Finally, I want to plot all prediction points and all ground_truth points as I already did. What I wanna do, is to plot a line between each prediction and ground_truth point. So that the line is a connection between the prediction point x1,y1 and the ground_truth point x2,y2. For a better understanding I attached an image. The black lines (created via paint) is what I want to do.
This is what I already have:
fig, ax = plt.subplots()
ax.plot(pred,'ro', label='Prediction', color = 'g')
ax.plot(GT,'^', label='Ground Truth', color = 'r' )
plt.xlabel('a')
plt.ylabel('b')
plt.title('test')
plt.xticks(np.arange(-1, 100, 5))
plt.style.use('ggplot')
plt.legend()
plt.show()
I guess the easiest and most understandable solution is to plot the respective lines between pred and GT in a loop.
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['legend.numpoints'] = 1
#generate some random data
pred = np.random.rand(10)*70
GT = pred+(np.random.randint(8,40,size= len(pred))*2.*(np.random.randint(2,size=len(pred))-.5 ))
fig, ax = plt.subplots(figsize=(6,4))
# plot a black line between the
# ith prediction and the ith ground truth
for i in range(len(pred)):
ax.plot([i,i],[pred[i], GT[i]], c="k", linewidth=0.5)
ax.plot(pred,'o', label='Prediction', color = 'g')
ax.plot(GT,'^', label='Ground Truth', color = 'r' )
ax.set_xlim((-1,10))
plt.xlabel('a')
plt.ylabel('b')
plt.title('test')
plt.legend()
plt.show()
You can plot each line as a separate plot. You could make a loop and call plot for each line connecting the two points. However you could also give the plot(x, y, ...) two 2d arrays as arguments. Each column in x will correspond to the same column in y and are represented by a line in the plot. So you'll need to generate these two. It could look something like this:
L = len(pred)
t = np.c_[range(L), range(L)].T
ax.plot(t, np.c_[pred, GT].T, '-k')
You can achieve this using matplotlib errorbar (http://matplotlib.org/1.2.1/examples/pylab_examples/errorbar_demo.html), with the idea of drawing error bars around the average of the two lines you are plotting:
Here is a minimal example to show my idea:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# example data
x = np.arange(0.1,10, 0.5)
y1 = pd.Series(np.exp(-x), index = x)
y2 = pd.Series(np.exp(-x)+ np.sin(x), index = x)
avg_line = (y1 + y2)*0.5
err = (avg_line - y1).abs()
fig, ax = plt.subplots(1)
y1.plot(marker = 'o', label='Prediction', color = 'g', linestyle = '', ax = ax)
y2.plot(marker = '^', label='Ground Truth', color = 'r', linestyle = '', ax = ax)
ax.errorbar(x, avg_line.values, yerr=err.values, fmt= 'none', ecolor = 'k', barsabove = False, capthick=0)
plt.style.use('ggplot')
ax.legend()
Hope this solves your problem.

matplotlib: 2 different legends on same graph

I have a plot where different colors are used for different parameters, and where different line styles are used for different algorithms. The goal is to compare the results of the different algorithms performed with similar parameters. It means in total I use 4 different colors, and 3 different line styles, for a total of 12 plots on the same graph.
I actually build the legend based on colors, associating each color with the corresponding parameter. Now I'd like to display a second legend on the same graph, with the meaning of each line style. It is possible to achieve that? How?
Here is what my code looks like actually:
colors = ['b', 'r', 'g', 'c']
cc = cycle(c)
for p in parameters:
d1 = algo1(p)
d2 = algo2(p)
d3 = algo3(p)
pyplot.hold(True)
c = next(cc)
pyplot.plot(d1, '-', color=c, label="d1")
pyplot.plot(d1, '--', color=c)
pyplot.plot(d2, '.-', color=c)
pyplot.legend()
There's a section in the matplotlib documentation on that exact subject.
Here's code for your specific example:
import itertools
from matplotlib import pyplot
colors = ['b', 'r', 'g', 'c']
cc = itertools.cycle(colors)
plot_lines = []
for p in parameters:
d1 = algo1(p)
d2 = algo2(p)
d3 = algo3(p)
pyplot.hold(True)
c = next(cc)
l1, = pyplot.plot(d1, '-', color=c)
l2, = pyplot.plot(d2, '--', color=c)
l3, = pyplot.plot(d3, '.-', color=c)
plot_lines.append([l1, l2, l3])
legend1 = pyplot.legend(plot_lines[0], ["algo1", "algo2", "algo3"], loc=1)
pyplot.legend([l[0] for l in plot_lines], parameters, loc=4)
pyplot.gca().add_artist(legend1)
Here's an example of its output:
Here is also a more "hands-on" way to do it (i.e. interacting explicitely with any figure axes):
import itertools
from matplotlib import pyplot
fig, axes = plt.subplot(1,1)
colors = ['b', 'r', 'g', 'c']
cc = itertools.cycle(colors)
plot_lines = []
for p in parameters:
d1 = algo1(p)
d2 = algo2(p)
d3 = algo3(p)
c = next(cc)
axes.plot(d1, '-', color=c)
axes.plot(d2, '--', color=c)
axes.plot(d3, '.-', color=c)
# In total 3x3 lines have been plotted
lines = axes.get_lines()
legend1 = pyplot.legend([lines[i] for i in [0,1,2]], ["algo1", "algo2", "algo3"], loc=1)
legend2 = pyplot.legend([lines[i] for i in [0,3,6]], parameters, loc=4)
axes.add_artist(legend1)
axes.add_artist(legend2)
I like this way of writing it since it allows potentially to play with different axes in a less obscure way. You can first create your set of legends, and then add them to the axes you want with the method "add_artist". Also, I am starting with matplotlib, and for me at least it is easier to understand scripts when objets are explicited.
NB: Be careful, your legends may be cutoff while displaying/saving. To solve this issue, use the method axes.set_position([left, bottom, width, length]) to shrink the subplot relatively to the figure size and make the legends appear.
What about using a twin ghost axis?
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
colors = ['b', 'r', 'g', ]
styles = ['-', '--', '-.']
for cc, col in enumerate(colors):
for ss, sty in enumerate(styles):
print(cc, ss)
ax.plot([0, 1], [cc, ss], c=colors[cc], ls=styles[ss])
for cc, col in enumerate(colors):
ax.plot(np.NaN, np.NaN, c=colors[cc], label=col)
ax2 = ax.twinx()
for ss, sty in enumerate(styles):
ax2.plot(np.NaN, np.NaN, ls=styles[ss],
label='style ' + str(ss), c='black')
ax2.get_yaxis().set_visible(False)
ax.legend(loc=1)
ax2.legend(loc=3)
plt.show()
You can also use line.get_label()
import matplotlib.pyplot as plt
plt.figure()
colors = ['b', 'r', 'g', 'c']
parameters = [1,2,3,4]
for p in parameters:
color = colors[parameters.index(p)]
plt.plot([1,10],[1,p], '-', c=color, label='auto label '+str(p))
lines = plt.gca().get_lines()
include = [0,1]
legend1 = plt.legend([lines[i] for i in include],[lines[i].get_label() for i in include], loc=1)
legend2 = plt.legend([lines[i] for i in [2,3]],['manual label 3','manual label 4'], loc=4)
plt.gca().add_artist(legend1)
plt.show()
import matplotlib.pyplot as plt
plt.figure()
colors = ['b', 'r', 'g', 'c']
parameters = [1,2,3,4]
for p in parameters:
color = colors[parameters.index(p)]
plt.plot([1,10],[1,p], '-', c=color, label='auto label '+str(p))
lines = plt.gca().get_lines()
include = [0,1]
legend1 = plt.legend([lines[i] for i in include],[lines[i].get_label() for i in include], loc=1)
legend2 = plt.legend([lines[i] for i in [2,3]],['manual label 3','manual label 4'], loc=4)
plt.gca().add_artist(legend1)
plt.show()

Categories