Map individual lines across two y axes in Matplotlib - python

I am trying to show cause and effect by displaying lines that are connected by two separate y axes.
eps_surprise = [56.15, 80.41, 218.48, 5.67, 2.99, 5.67]
stock_movement = [-5.72, 14.52, 18.78, -6.77, 6.03, -6.77]
fig, ax = plt.subplots()
y = list(zip(eps_surprise,stock_movement))
for i in y:
ax.plot((0,1),i)
plt.show()
As you can see in the above chart, I want to include a second y axis that would have a range limited to the min and max of stock_movement. However, my attempts in doing so do not result in the desired chart, as what is plotted is then based on the second axis alone, and not the first.
import matplotlib.pyplot as plt
eps_surprise = [56.15, 80.41, 218.48, 5.67, 2.99, 5.67]
stock_movement = [-5.72, 14.52, 18.78, -6.77, 6.03, -6.77]
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.set_ylim(min(eps_surprise),max(eps_surprise))
ax2.set_ylim(min(stock_movement),max(stock_movement))
y = list(zip(eps_surprise,stock_movement))
for i in y:
plt.plot((0,1),i)
plt.show()
The desired output would look something like as follows:

Not sure if this is what you are asking exactly, but given your question it's the best I can picture.
import matplotlib.pyplot as plt
eps_surprise = [56.15, 80.41, 218.48, 5.67, 2.99, 5.67]
stock_movement = [-5.72, 14.52, 18.78, -6.77, 6.03, -6.77]
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.set_ylim(min(eps_surprise),max(eps_surprise))
ax2.set_ylim(min(stock_movement),max(stock_movement))
# plot eps surprise and stock movement on the two separate axes
l1 = ax1.plot(eps_surprise, 'k--', label = 'eps surprise')
l2 = ax2.plot(stock_movement, 'r', label = 'stock movement')
# create legend for both axes
lns = [l1[0], l2[0]]
labs = [l.get_label() for l in lns]
ax2.legend(lns, labs)
ax1.set_ylabel('eps surprise')
ax2.set_ylabel('stock movement')
Honestly, though, if I were trying to show a relationship between two variables, I'd be more tempted to make a scatter plot or a regression.
from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
regressor.fit([[i,] for i in eps_surprise],
[[i,] for i in stock_movement])
x = [[min(eps_surprise)], [max(eps_surprise)]]
plt.plot(eps_surprise, stock_movement, '.')
plt.plot(x, regressor.predict(x))
plt.grid()
plt.xlabel('eps surprise')
plt.ylabel('stock movement')
UPDATE
Based on the update in your question of how you want your original figure to look, here's how I would do it. The second set of axes are purely for labelling and instead I scaled the stock movement values to fit on your ax1 scale.
import matplotlib.pyplot as plt
def map_to_eps(stock_movement, eps_surprise):
ybounds = [min(stock_movement), max(stock_movement)]
dy = ybounds[1] - ybounds[0]
xbounds = [min(eps_surprise), max(eps_surprise)]
dx = xbounds[1] - xbounds[0]
return([(y - ybounds[0]) / dy * dx + xbounds[0] for y in stock_movement], dy, dx)
eps_surprise = [56.15, 80.41, 218.48, 5.67, 2.99, 5.67]
stock_movement = [-5.72, 14.52, 18.78, -6.77, 6.03, -6.77]
(stock_eps, dstock, deps) = map_to_eps(stock_movement, eps_surprise)
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.set_ylim(min(eps_surprise) - 0.05 * deps,max(eps_surprise) + 0.05 * deps)
ax2.set_ylim(min(stock_movement) - 0.05 * dstock ,max(stock_movement) + 0.05 * dstock)
y = list(zip(eps_surprise,stock_eps))
for i in y:
ax1.plot((0,1),i)
plt.xticks([])
ax1.set_ylabel('eps surprise')
ax2.set_ylabel('stock movement')

Related

bar x-tick not as same as the image

Im not sure if i use the wrong data or if there is and edit i need to do and not seeing it. It would be nice if someone could take a look at the code. The problem here is that yerr at the first bar is at x=0 and in the image the yerr is somewhere around 2.5
Does someone know what i did wrong or forgot to edit?
the end result should be:
my code:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1)
y_raw = np.random.randn(1000).cumsum() + 15
x_raw = np.linspace(0, 24, y_raw.size)
x_pos = x_raw.reshape(-1, 100).min(axis=1)
y_avg = y_raw.reshape(-1, 100).mean(axis=1)
y_err = y_raw.reshape(-1, 100).ptp(axis=1)
bar_width = x_pos[1] - x_pos[0]
x_pred = np.linspace(0, 30)
y_max_pred = y_avg[0] + y_err[0] + 2.3 * x_pred
y_min_pred = y_avg[0] - y_err[0] + 1.2 * x_pred
barcolor, linecolor, fillcolor = 'wheat', 'salmon', 'lightblue'
fig, axes = fig, ax = plt.subplots()
axes.set_title(label="Future Projection of Attitudes", fontsize=15)
plt.xlabel('Minutes since class began', fontsize=12)
plt.ylabel('Snarkiness (snark units)', fontsize=12)
fig.set_size_inches(8, 6, forward=True)
axes.fill_between(x_pred, y_min_pred, y_max_pred ,color='lightblue')
axes.plot(x_raw, y_raw, color='salmon')
vert_bars = axes.bar(x_pos, y_avg, yerr=y_err, color='wheat', width = bar_width, edgecolor='grey',error_kw=dict(lw=1, capsize=5, capthick=1, ecolor='gray'))
axes.set(xlim=[0, 30], ylim=[0,100])
plt.show()
yerr is meant to be the difference between the mean and the min/max. Now you're using the full difference between max and min. You might divide it by 2 to get a better approximation. To obtain the exact values, you could calculate them explicitly (see code example).
Further, by default, the bars are center aligned vs their x-position. You can use align='edge' to left-align them (as x_pos is calculated as the minimum of the range the bar represents). You could also set clip_on=False in the err_kw to make sure the error bars are never clipped by the axes.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1)
y_raw = np.random.randn(1000).cumsum() + 15
x_raw = np.linspace(0, 24, y_raw.size)
x_pos = x_raw.reshape(-1, 100).min(axis=1)
y_avg = y_raw.reshape(-1, 100).mean(axis=1)
y_min = y_raw.reshape(-1, 100).min(axis=1)
y_max = y_raw.reshape(-1, 100).max(axis=1)
bar_width = x_pos[1] - x_pos[0]
x_pred = np.linspace(0, 30)
y_max_pred = y_avg[0] + y_err[0] + 2.3 * x_pred
y_min_pred = y_avg[0] - y_err[0] + 1.2 * x_pred
barcolor, linecolor, fillcolor = 'wheat', 'salmon', 'lightblue'
fig, ax = plt.subplots(figsize=(8, 6))
ax.set_title(label="Future Projection of Attitudes", fontsize=15)
ax.set_xlabel('Minutes since class began', fontsize=12)
ax.set_ylabel('Snarkiness (snark units)', fontsize=12)
ax.fill_between(x_pred, y_min_pred, y_max_pred, color='lightblue')
ax.plot(x_raw, y_raw, color='salmon')
vert_bars = ax.bar(x_pos, y_avg, yerr=(y_avg - y_min, y_max - y_avg),
color='wheat', width=bar_width, edgecolor='grey', align='edge',
error_kw=dict(lw=1, capsize=5, capthick=1, ecolor='grey', clip_on=False))
ax.set(xlim=[0, 30], ylim=[0, 100])
plt.tight_layout()
plt.show()

Automatic add text to matplotlib plot in Python

I try to produce a plot and want to automatically add text (in this case is percentage) to each circle in correspond to each y axis types. Any help would be very helpful.
# import libraries
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
# Make some data
index=['Stream flow',
'Soil moisture',
'Water indices',
'Others',
'Temperature',
'Precipitation',
'Vegetative indices']
value=[2.13, 6.38, 10.64, 12.77, 17.73, 21.99, 28.37]
# create dataframe
percentages = pd.Series(value,index=index)
df = pd.DataFrame({'percentage' : percentages})
df = df.sort_values(by='percentage')
# we first need a numeric placeholder for the y axis
my_range=list(range(1,len(df.index)+1))
fig, ax = plt.subplots(figsize=(15,8))
# create for each expense type an horizontal line that starts at x = 0 with the length
plt.hlines(y=my_range, xmin=0, xmax=df['percentage']-0.5, color='black', alpha=0.8, linewidth=1)
# create for each expense type a dot at the level of the expense percentage value
line=plt.plot(df['percentage'], my_range, "o", markersize=30, color='#fd8c00', alpha=0.6, linewidth=0.3)
# set labels
ax.set_xlabel('Percentage', fontsize=15)
ax.set_ylabel('')
# set axis
ax.tick_params(axis='both', which='major', labelsize=14)
plt.yticks(my_range, df.index)
ax.set_xlim(0,30)
You can use matplotlib.axes.Axes.text:
x_space = 0.4
y_space = 0.05
fontsize = 7
for y_i, val in enumerate(value, 1):
ax.text(x = val - x_space, y = y_i - y_space, s = f'{val}%', fontsize = fontsize)
You have to adjust x_space, y_space and fontsize in order to fit properly the text within the circles.
Complete code
# import libraries
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
# Make some data
index=['Stream flow',
'Soil moisture',
'Water indices',
'Others',
'Temperature',
'Precipitation',
'Vegetative indices']
value=[2.13, 6.38, 10.64, 12.77, 17.73, 21.99, 28.37]
# create dataframe
percentages = pd.Series(value,index=index)
df = pd.DataFrame({'percentage' : percentages})
df = df.sort_values(by='percentage')
# we first need a numeric placeholder for the y axis
my_range=list(range(1,len(df.index)+1))
fig, ax = plt.subplots(figsize=(15,8))
# create for each expense type an horizontal line that starts at x = 0 with the length
plt.hlines(y=my_range, xmin=0, xmax=df['percentage']-0.5, color='black', alpha=0.8, linewidth=1)
# create for each expense type a dot at the level of the expense percentage value
line=plt.plot(df['percentage'], my_range, "o", markersize=30, color='#fd8c00', alpha=0.6, linewidth=0.3)
# set labels
ax.set_xlabel('Percentage', fontsize=15)
ax.set_ylabel('')
# set axis
ax.tick_params(axis='both', which='major', labelsize=14)
plt.yticks(my_range, df.index)
ax.set_xlim(0,30)
x_space = 0.4
y_space = 0.05
for y_i, val in enumerate(value, 1):
ax.text(x = val - x_space, y = y_i - y_space, s = f'{val:>5.2f}%', fontsize = 7)
plt.show()
Same code as above, but with increased circle radius and font, in order to improve readability.
# import libraries
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
# Make some data
index=['Stream flow',
'Soil moisture',
'Water indices',
'Others',
'Temperature',
'Precipitation',
'Vegetative indices']
value=[2.13, 6.38, 10.64, 12.77, 17.73, 21.99, 28.37]
# create dataframe
percentages = pd.Series(value,index=index)
df = pd.DataFrame({'percentage' : percentages})
df = df.sort_values(by='percentage')
# we first need a numeric placeholder for the y axis
my_range=list(range(1,len(df.index)+1))
fig, ax = plt.subplots(figsize=(15,8))
# create for each expense type an horizontal line that starts at x = 0 with the length
plt.hlines(y=my_range, xmin=0, xmax=df['percentage']-0.85, color='black', alpha=0.8, linewidth=1)
# create for each expense type a dot at the level of the expense percentage value
line=plt.plot(df['percentage'], my_range, "o", markersize=50, color='#fd8c00', alpha=0.6, linewidth=0.3)
# set labels
ax.set_xlabel('Percentage', fontsize=15)
ax.set_ylabel('')
# set axis
ax.tick_params(axis='both', which='major', labelsize=14)
plt.yticks(my_range, df.index)
ax.set_xlim(0,30)
ax.set_ylim(0, len(value) + 1)
x_space = 0.75
y_space = 0.06
fontsize = 12
for y_i, val in enumerate(value, 1):
ax.text(x = val - x_space, y = y_i - y_space, s = f'{val:>5.2f}%', fontsize = fontsize)
plt.show()
Even better, you can use matplotlib.axes.Axes.annotate to get rid of x_space and y_space:
fontsize = 12
for y_i, x_i in enumerate(value, 1):
ax.annotate(f'{x_i:>5.2f}%', xy = (x_i, y_i), xytext = (0, 0), textcoords = 'offset points', ha = 'center', va = 'center', fontsize = fontsize)
You still have to adjust the fontsize to properly fit the radius of the circles.

ax.annotate text partially appearing outside the figure box

Apologies, rather unskilled with programming and stackoverflow too. I am drawing bar plots on some data and have managed to add percentages beside the bars, using ax.annotate. However for the bar with highest responses I always get part of the percentage number outside the figure box, as per image below. Have tried different ideas but none worked to fix this. Looking for some suggestions on how to fix this.
Here is my code
from matplotlib import pyplot as plt
import seaborn as sns
def plot_barplot(df):
plt.rcParams.update({'font.size': 18})
sns.set(font_scale=2)
if (len(df) > 1):
fig = plt.figure(figsize=(12,10))
ax = sns.barplot(x='count', y=df.columns[0], data=df, color='blue')
else:
fig = plt.figure(figsize=(5,7))
ax = sns.barplot(x=df.columns[0], y='count', data=df, color='blue')
fig.set_tight_layout(True)
plt.rcParams.update({'font.size': 14})
total = df['count'].sum()
for p in ax.patches:
percentage ='{:.2f}%'.format(100 * p.get_width()/total)
print(percentage)
x = p.get_x() + p.get_width() + 0.02
y = p.get_y() + p.get_height()/2
ax.annotate(percentage, (x, y))
Dataframe looks like this
I would suggest you increase the axes' margins (in the x direction in that case). That is the space there is between the maximum of your data and the maximum scale on the axis. You will have to play around with the value depending on your needs, but it looks like a value of 0.1 or 0.2 should be enough.
add:
plt.rcParams.update({'axes.xmargin': 0.2})
to the top of your function
full code:
from matplotlib import pyplot as plt
import seaborn as sns
import pandas as pd
def plot_barplot(df):
plt.rcParams.update({'font.size': 18})
plt.rcParams.update({'axes.xmargin': 0.1})
sns.set(font_scale=2)
if (len(df) > 1):
fig = plt.figure(figsize=(12, 10))
ax = sns.barplot(x='count', y=df.columns[0], data=df, color='blue')
else:
fig = plt.figure(figsize=(5, 7))
ax = sns.barplot(x=df.columns[0], y='count', data=df, color='blue')
fig.set_tight_layout(True)
plt.rcParams.update({'font.size': 14})
total = df['count'].sum()
for p in ax.patches:
percentage = '{:.2f}%'.format(100 * p.get_width() / total)
print(percentage)
x = p.get_x() + p.get_width() + 0.02
y = p.get_y() + p.get_height() / 2
ax.annotate(percentage, (x, y))
df = pd.DataFrame({'question': ['Agree', 'Strongly agree'], 'count': [200, 400]})
plot_barplot(df)
plt.show()

Matplotlib, multiple line plots axis annotation

I have five points with multiple x(t), y(t) and z(t) coordinates that I want to plot using matplotlib in one figure. I've wrote this python code:
import matplotlib.pyplot as plt
fig = plt.figure(4)
fig.set_facecolor('white')
yprops = dict(rotation=0,
horizontalalignment='right',
verticalalignment='center')
axprops = dict(yticks=[])
targets = ['centroid_label','centroid_base','centroid_apex','midgland_inferior','midgland_left','midgland_right','midgland_superior']
count = 0.0
axlist = []
for target in targets:
x,y,z = getXYZMotion_with_compensation(case,target)
ax = fig.add_axes([0.1, count, 0.8, 0.2], **axprops)
axlist.append(ax)
axprops['sharex'] = ax
axprops['sharey'] = ax
ax.plot(list_of_times_in_minutes, x)
ax.plot(list_of_times_in_minutes, y)
ax.plot(list_of_times_in_minutes, z)
ax.set_ylabel(target, **yprops)
count = count + 0.2
#turn off x ticklabels for all but the lower axes
for ax in axlist:
plt.setp(ax.get_xticklabels(), visible=False)
plt.show()
which produces:
However, I want to have x axis description for all axis below the lowest graph and y axis description for every sub-figure from -3 to 3. How can I achieve that?

Graphing tan in matplotlib

I have the following code:
from mpl_toolkits.axes_grid.axislines import SubplotZero
from matplotlib.transforms import BlendedGenericTransform
import matplotlib.pyplot as plt
import numpy
if 1:
fig = plt.figure(1)
ax = SubplotZero(fig, 111)
fig.add_subplot(ax)
ax.axhline(linewidth=1.7, color="black")
ax.axvline(linewidth=1.7, color="black")
plt.xticks([1])
plt.yticks([])
ax.text(0, 1.05, 'y', transform=BlendedGenericTransform(ax.transData, ax.transAxes), ha='center')
ax.text(1.05, 0, 'x', transform=BlendedGenericTransform(ax.transAxes, ax.transData), va='center')
for direction in ["xzero", "yzero"]:
ax.axis[direction].set_axisline_style("-|>")
ax.axis[direction].set_visible(True)
for direction in ["left", "right", "bottom", "top"]:
ax.axis[direction].set_visible(False)
x = numpy.linspace(-1, 1, 10000)
ax.plot(x, numpy.tan(2*(x - numpy.pi/2)), linewidth=1.2, color="black")
plt.ylim(-5, 5)
plt.savefig('graph.png')
which produces this graph:
As you can see, not only is the tan graph sketched, but a portion of line is added to join the asymptotic regions of the tan graph, where an asymptote would normally be.
Is there some built in way to skip that section? Or will I graph separate disjoint domains of tan that are bounded by asymptotes (if you get what I mean)?
Something you could try: set a finite threshold and modify your function to provide non-finite values after those points. Practical code modification:
yy = numpy.tan(2*(x - numpy.pi/2))
threshold = 10000
yy[yy>threshold] = numpy.inf
yy[yy<-threshold] = numpy.inf
ax.plot(x, yy, linewidth=1.2, color="black")
Results in:
This code creates a figure and one subplot for tangent function. NaN are inserted when cos(x) is tending to 0 (NaN means "Not a Number" and NaNs are not plotted or connected).
matplot-fmt-pi created by k-donn(https://pypi.org/project/matplot-fmt-pi/) used to change the formatter to make x labels and ticks correspond to multiples of π/8 in fractional format.
plot formatting (grid, legend, limits, axis) is performed as commented.
import matplotlib.pyplot as plt
import numpy as np
from matplot_fmt_pi import MultiplePi
fig, ax = plt.subplots() # creates a figure and one subplot
x = np.linspace(-2 * np.pi, 2 * np.pi, 1000)
y = np.tan(x)
y[np.abs(np.cos(x)) <= np.abs(np.sin(x[1]-x[0]))] = np.nan
# This operation inserts a NaN where cos(x) is reaching 0
# NaN means "Not a Number" and NaNs are not plotted or connected
ax.plot(x, y, lw=2, color="blue", label='Tangent')
# Set up grid, legend, and limits
ax.grid(True)
ax.axhline(0, color='black', lw=.75)
ax.axvline(0, color='black', lw=.75)
ax.set_title("Trigonometric Functions")
ax.legend(frameon=False) # remove frame legend frame
# axis formatting
ax.set_xlim(-2 * np.pi, 2 * np.pi)
pi_manager = MultiplePi(8) # number= ticks between 0 - pi
ax.xaxis.set_major_locator(pi_manager.locator())
ax.xaxis.set_major_formatter(pi_manager.formatter())
plt.ylim(top=10) # y axis limit values
plt.ylim(bottom=-10)
y_ticks = np.arange(-10, 10, 1)
plt.yticks(y_ticks)
fig
[![enter image description here][1]][1]plt.show()

Categories