Zero line for primary and secondary axis - matplotlib - python

I am trying to display 2 data on a plot, each of them is assigned on primary and secondary axis respectively.
Here are the codes
ax2 = ax1.twinx()
ax1.plot(df['year'], df['present_value']/1000000, 'g-')
ax2.plot(df['year'], df['cum_pv']/1000000, 'b-')
ax1.set_xlabel('X data')
ax1.set_ylabel('Y1 data', color='g')
ax2.set_ylabel('Y2 data', color='b')
plt.show()
But it displays a chart with not equal zero levels, (notice how 0 on primary is on a different level from 0 on secondary axis) `
How can I make the zero scale on the same level from my codes above?

You can use the set_ylim()-method of each axis to adjust them manually.
import matplotlib.pyplot as plt
y1 = [-1,2,3]
y2 = [-10,-5,10]
x = [1,2,3]
fig, ax1 = plt.subplots() # opens a figure with a single axis
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
ax1.plot(x,y1, 'g-')
ax2.plot(x,y2, 'b-')
plt.show()
Here is a way, which assumes that there actually is a zero in both y-axes
# range
ax1_range = ax1.get_ylim()[1] - ax1.get_ylim()[0]
fct = (0 - ax1.get_ylim()[0])/ax1_range
ax2_ylim = ax2.get_ylim()
# calculate new values for the second axis
ax2_ylim_new = (ax2_ylim[0],(0 - ax2_ylim[0]) / fct + ax2_ylim[0])
# set new limits
ax2.set_ylim( ax2_ylim_new )
plt.show()
I scaled the second y-axis ax2 so that it exhibits the 0 at the same height of the axis as the first y-axis ax1. This becomes more tricky if one of the axes does not contain zero (so the limits do not have a change of the sign). You could write a little function, which handles the different cases if you like to have a generic solution.

Related

How to scale an axis in matplotlib and avoid axes plotting over each other

I have a dataframe:
Date,a,b,c
01/11/21,10,5,34
02/11/21,11,5.5,32
03/11/21,12,6,311
04/11/21,13,6.5,32
05/11/21,14,7,56
06/11/21,15,7.5,676
07/11/21,16,8,534
When I plot it with the code below (3 axes: a,b and c) - axes a and b identically plot over each other, because they are of the same scale.
As below:
How can my code be modified so that each plotted line is obvious?
df = pd.read_csv("plot.csv")
yax1 ='a'
yax2 = 'b'
yax3 = 'c'
ax1 = df.plot(x='Date', y=yax1, color='black', figsize=(12,7), legend=False)
ax1.set_ylabel(yax1)
ax1.yaxis.label.set_color('black')
ax1.set_xlabel('Date')
plt.subplots_adjust(right=0.85)
ax2 = ax1.twinx()
ax2.spines['right'].set_position(('axes', 1.0))
df[yax2].plot(ax=ax2, color='green')
ax2.set_ylabel(yax2)
ax2.yaxis.label.set_color('green')
ax2.legend([ax1.get_lines()[0], ax2.get_lines()[0]], [yax1, yax2], loc='upper left') # loc can be 'best' also
ax2.spines['right'].set_color('green')
ax3 = ax1.twinx()
ax3.spines['right'].set_position(('axes', 1.1))
df[yax3].plot(ax=ax3, color='red')
ax3.yaxis.label.set_color('red')
ax3.legend([ax1.get_lines()[0], ax2.get_lines()[0], ax3.get_lines()[0]],[yax1, yax2, yax3], loc='upper left')
ax3.spines['right'].set_color('red')
plt.tight_layout()
plt.show()
You could consider changing the y-axis limits for either ax1 (a) or ax2 (b). As an example, using the piece of code below I scale the y-axis of ax1:
# Get current y-axis limits
ax1_y_old_lower, ax1_y_old_upper = ax1.get_ylim()
# Scale the y-axis limits
scale_factor = 0.1
ax1_y_new_lower = (1 - scale_factor) * ax1_y_old_lower
ax1_y_new_upper = (1 + scale_factor) * ax1_y_old_upper
# Set the scaled y-axis limits to be the new limits
ax1.set_ylim(ax1_y_new_lower, ax1_y_new_upper)
As you can see, I first get the current y-axis limits and subsequently scale them using a scaling factor (i.e. scale_factor). Of course, you can change the value of this scaling factor to get the desired result.
For reference, this leads to the following output graph:

Changing Index of the axis in a Graph in matplotlib

I have written a code which will plot a graph of Time VS Amplitude. Now , I want to change the index which is on the horizontal axis. I want to know how I can do it for a single plot and also for the subplots. I want the range of the horizontal axis to be from 0 to 2*pi.
#the following code was written for plotting
fig, (ax1, ax2 ,ax3) = plt.subplots(3 ,constrained_layout = True)
fig.suptitle('AMPLITUDE MODULATION' ,color = 'Red')
ax1.plot(message_signal)
ax1.set_title('Message Signal' ,color = 'green')
I expect the x-axis to go from 0 to 2*pi only. In short, I want to customize the indexing of the x-axis
You can use xlim to set the limits of the x-axis for whole plot or specific axes, e.g. plt.xlim(0, 1) or ax1.set_xlim(0, 1).
Here I set the limits for the x-axis to be [0, 3*pi]
fig, (ax1, ax2 ,ax3) = plt.subplots(3, constrained_layout = True)
fig.suptitle('AMPLITUDE MODULATION', color = 'Red')
x = np.linspace(0, 2*np.pi, 1000)
ax1.plot(x, np.sin(x))
ax1.set_title('Message Signal', color = 'green')
ax1.set_xlim(0, 3*np.pi)

How to align logarithmic scale ticks across subplots?

I want to fix the position of the ticks on the logarithmic scale, such that they are the same in each subplot (see red annotation in image).
My code looks like this:
ax = fig.add_subplot(2,2, axis)
ax2 = ax.twinx()
ax2.set_yscale('log')
ax2.set_ylim(0,100)
Right now, set_yscale=('log') optimizes the tick spacing for each subplot. I prefer to adopt the tick spacing of the upper right subplot.
You can achieve this by getting the limits of the left twin axis and setting it as the limits of the right twin axis.
Consider the following working example. Follow this procedure for the subplots you want to align the axes of.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8, 3))
axl = fig.add_subplot(121)
axr = fig.add_subplot(122)
ax1 = axl.twinx()
ax1.plot(np.logspace(-2, 3, 5))
ax1.set_yscale('log')
ax2 = axr.twinx()
ax2.plot(np.logspace(0, 3, 5))
ax2.set_yscale('log')
ax2.set_ylim(ax1.get_ylim()) # <-- This is the key line
plt.tight_layout()
plt.show()
OP's solution:
Plot a dummy curve and set alpha=0. Make sure the curve spans y_min and y_max.
fig = plt.figure()
axes = [1,2,3,4]
for axis in axes:
ax = fig.add_subplot(2,2, axis)
ax2 = ax.twinx()
ax2.set_yscale('log')
ax2.plot(x_dummy, y_dummy, alpha=0) # <-- dummy plot
x_real, y_real = func_that_loads_data() # <-- your interesting plot
curve1 = ax2.plot(x_real, y_real)
plt.show()
The solution provided by Sheldore was impractical to implement because I plot my data using a for-loop (unavoidable unless I escalate the number of variables).
Since I overwrite the ax variable on every iteration, I would have to save the y-limit as a global variable. Read here why global variables should be avoided.
ax = fig.add_subplot(2,2, axis)
ax2 = ax.twinx()
ax2.set_yscale('log')
if axis == 1:
global yscale
yscale = ax2.get_ylim() # <-- where the magic happens
elif axis > 1:
ax2.set_ylim(yscale)

Preserve padding while setting an axis limit in matplotlib

Setting xlim and ylim for axis in pyplot removes the padding. How to set them without changing the padding?
Example:
fig, ax = plt.subplots()
x = np.linspace(0, 200, 500)
ax.set_ylim(ymax=100)
line = ax.plot(x, data, '--', linewidth=2, label='foo bar')
plt.show()
In the plot shown, x axis will have a padding while y axis don't. How to make them both have padding while having the ylim I want?
Axes.set_ymargin and Axes.set_ylim are mutually exclusive. Setting a limit to an axis overwrites the margin.
There are two options to have a margin (padding).
a. use margins
It's possible to adapt the margin using
ax.set_ymargin(0.1) or ax.margins(y=0.1)
where 0.1 would be a 10% margin on both axis ends. (Same for x axis of course). The drawback here is that the margin is always symmetric.
b. use limits
Using the limits set by ax.set_ylim(0, 100) and adapt them to the needs.
E.g. if data is the data to plot in form of a numpy array, and we want to have a 10% margin to the bottom and a 40% margin to the top, we could use
ymin = data.min()-0.1*(data.max()-data.min())
ymax = data.max()+0.4*(data.max()-data.min())
ax.set_ylim((ymin, ymax))
It would of course equally be possible to simply set ymax to ymax = 100, if this is desired.
With matplotlib 3.5, I used autoscaling as follows;
axs[row,column].plot(divs['Dividends'], c='red',linewidth=1, marker='.', mec='blue',mfc='blue')
axs[row,column].set_ylim(bottom = 0)
axs[row,column].autoscale()
Solved this problem for me. See attached pics of the graphs for the difference autoscaling did.
Using .margins() with a value or 'tight=True' or .set_ymargin() didn't seem to do anything no matter what values I used to pad.
Changing the lower or bottom limit to <0 moved the Zero line well up the y axis when dividends in my examples are close to zero.
Graph with Autoscaling
Graph without Autoscaling
You can modify the ax.dataLim bounding box and reapply ax.autoscale_view()
Before:
fig, ax = plt.subplots()
x = np.linspace(0, 10, 11)
line = ax.plot(x, x, '--', linewidth=2, label='foo bar')
After:
pts = ax.dataLim.get_points() # numpy array [[xmin, ymin], [xmax, ymax]]
pts[1, 1] = 11 # new ymax
ax.dataLim.set_points(pts)
ax.autoscale_view()

how to format axis in matplotlib.pyplot and include legend?

Got this function for the graph, want to format axis so that the graph starts from (0,0), also how do I write legends so I can label which line belongs to y1 and which to y2 and label axis.
import matplotlib.pyplot as plt
def graph_cust(cust_type):
"""function produces a graph of day agaist customer number for a given customer type"""
s = show_all_states_list(cust_type)
x = list(i['day']for i in s)
y1 = list(i['custtypeA_nondp'] for i in s)
y2 = list(i['custtypeA_dp']for i in s)
plt.scatter(x,y1,color= 'k')
plt.scatter(x,y2,color='g')
plt.show()
Thanks
You can set the limits on either axis using plt.xlim(x_low, x_high). If you do not want to manually set the upper limit (e.g your happy with the current upper limit) then try:
ax = plt.subplot(111) # Create axis instance
ax.scatter(x, y1, color='k') # Same as you have above but use ax instead of plt
ax.set_xlim(0.0, ax.get_xlim()[1])
Note the slight difference here, we use an axis instance. This gives us the ability to return the current xlimits using ax.get_xlim() this returns a tuple (x_low, x_high) which we pick the second using [1].
A minimal example of a legend:
plt.plot(x, y, label="some text")
plt.legend()
For more on legends see any of these examples

Categories

Resources