pythonmatplotlibticker

How to align secondary y tick positions to primary y axis and change frequency of secondary y ticks?


I have a dataset with 3 columns: DEPTH, TVD_SCS_interp, Gamma_ray. I made a plot of DEPTH (on the left y-axis), TVD_SCS_interp (on the right y-axis) vs Gamma_ray (x axis) with the following code:

# Sample data (replace with your actual data)
data = {
        'DEPTH': [100, 120, 140, 160, 180, 200, 220, 240],
        'TVD_SCS_interp': [50.5, 60.2, 70.3, 80.7, 90.2, 100, 100.15, 120.9],
        'GR_N': [150, 150, 120, 130, 140, 130, 110, 100]
    }
ax1.plot("GR_N", "DEPTH", data = df, color = "#00be00").
ax1.set_ylim(bottom_depth, top_depth)
    
ax20.set_ylim(df.loc[df['DEPTH'] == bottom_depth, 'TVD_SCS_interp'].values[0], df.loc[df['DEPTH'] == top_depth, 'TVD_SCS_interp'].values[0])

I realized that secondary y (TVD_SCS) values do not correctly align with the corresponding primary y (DEPTH) values to the end of data points. This was because of data in ax2 has a different scale and arbitrary increments than ax1's (where it has linear scale). For example, TVD_SCS value 100.15 was not in front of DEPTH value 220 like in the data I provided).

So, I found a partial solution to this problem and achieved the following result:

ax1 = plt.subplot(gs[0])
ax2 = ax1.twinx()

def depth_to_tvd(depth):
    return df.loc[df['DEPTH'] == depth, 'TVD_SCS_interp'].values[0]

ax1.plot(df.Gamma_ray, df.DEPTH, color='b', label='DEPTH')
ax1.set_ylim(top_depth, bottom_depth)

ax2.set_ylim((ax1.get_ylim()))
ax1.yaxis.set_major_locator(ticker.MultipleLocator(10))
ax1.yaxis.set_minor_locator(ticker.MultipleLocator(1))
ax2.yaxis.set_major_locator(ticker.MultipleLocator(base=10))
ax2.yaxis.set_minor_locator(ticker.MultipleLocator(1))
ax2.set_yticklabels([Text(0, yval, f'{depth_to_tvd(yval):.2f}') for yval in ax1.get_yticks()])

enter image description here

I want to get tick positions for whole numbers (not decimals like in the picture) on the secondary y axis, and you see although I used

ax2.yaxis.set_major_locator(ticker.MultipleLocator(base=10))
ax2.yaxis.set_minor_locator(ticker.MultipleLocator(1)) 

it did not help me.

Could anyone help me to correctly align whole "TVD_SCS" values at the corresponding position of "DEPTH" and making the frequency of "TVD_SCS" ticks 10 as the primary y axis?


Solution

  • Finally, I figured out how to correctly align the secondary y axis values related to primary y axis, and displaying only 10th tick labels (2610, 2620, ..). In my dataset, TVD_SCS_interp has no or very few whole numbers corresponding to DEPTH. So, in order to change the major tick positions to every 10 m and minor tick positions to every 1 m, I created whole values using np.arange function.

    from scipy.interpolate import interp1d
    
    try: #if there is no DEPTH value for top_depth, find it by interpolation
        s = np.ceil(df[df['DEPTH'] == top_depth]['TVD_SCS_interp'].values[0])
    except:
        s = np.ceil(Int_TVD_SCS(top_depth))
    try: #the same here
        e = np.floor(df[df['DEPTH'] == bottom_depth]['TVD_SCS_interp'].values[0]) + 1
    except:
        e = np.floor(Int_TVD_SCS(bottom_depth))
        
    TVD_SCS_whole = np.arange(s, e)
    

    Since, we get ticks from the primary axis and change its labels in the secondary axis, we need to get the corresponding DEPTH values for those whole TVD_SCS's (every 1.0 and 10.0 ticks)by interpolation:

    Int_MD = interp1d(df["TVD_SCS_interp"], df["DEPTH"])
    MD_interp = Int_MD(TVD_SCS_whole)
    #Make MD_interp and TVD_SCS_whole a dataframe
    ....
    #Join that to actual dataset
    df = df.merge(df_MD_interp, on=['DEPTH','TVD_SCS_interp'], how='outer')
    df = df.sort_values(by='DEPTH')
    

    I will manually assign major and minor tick positions using ticker.FixedLocator()) function. Thus, I find major and minor tick values by the following code:

    df['TVD_SCS_interp_mod'] = df['TVD_SCS_interp'] % 10
    # Find major and minor ticks using vectorized operations
    y_ticks_major = df.loc[df['TVD_SCS_interp_mod'] == 0, 'DEPTH'].tolist()
    y_ticks_minor = df.loc[(df['TVD_SCS_interp_mod'] % 1 == 0) & (df['TVD_SCS_interp_mod'] != 0), 'DEPTH'].tolist()
    
    ax1 = plt.subplot(gs[1])
    ax20 = plt.subplot(gs[2])
    ax201 = ax20.twinx()
    ax1.plot("GR_N", "DEPTH", data = df, color = "#00be00", linewidth = 1, zorder=10, antialiased=True)
    ax1.yaxis.set_major_locator(ticker.MultipleLocator(10))
    ax1.yaxis.set_minor_locator(ticker.MultipleLocator(1))
    def depth_to_tvd(depth):
        return d.loc[d['DEPTH'] == depth, 'TVD_SCS_interp'].values[0]
    
    ax20.set_ylim(bottom_depth, top_depth)
    
    ax201.set_ylim((ax20.get_ylim()))
    ax20.yaxis.set_major_locator(ticker.FixedLocator(y_ticks_major))
    ax20.yaxis.set_minor_locator(ticker.FixedLocator(y_ticks_minor))
    ax201.set_yticks(ax20.get_yticks())
    ax201.set_yticklabels([Text(0, yval, f'{depth_to_tvd(yval):.0f}') for yval in ax20.get_yticks()])
    ax201.yaxis.set_major_locator(ticker.FixedLocator(y_ticks_major))
    ax201.yaxis.set_minor_locator(ticker.FixedLocator(y_ticks_minor))
    #other plot functions (tick length,...)
    ...
    

    The final plot is:

    enter image description here