Search code examples
pythonmatplotlibscalexticks

How can I have two different linear scales in x-axis in python?


Is there a way that I can set two different scales at the x-axis in a python plot?

I have following code:

import numpy as np
from matplotlib import pyplot as plt
import pandas as pd

data=pd.read_csv(file, names=['Wavenumber', 'Intensity'])

fig=plt.figure()
ax=fig.add_subplot(1,1,1)
ax.plot(data['Wavenumber'], data['Intensity'])
ax.invert_xaxis()
ax.set_xticks([4000,3000,2000,1600,1200,800,400])
plt.show()

This gives:

enter image description here

But I would like to have equal spacing between the ticks, so a linear scaling from 4000 to 2000 in steps of 1000, and then again linear scaling from 2000 to 400 in steps of 400. This should look like this:

enter image description here


Solution

  • Creating a custom scale in matplotlib can be quite an effort. As you only need two different linear scales, it is easier to use a workaround consisting of joining two subplots together. With many data points located near the boundary between the two scales (as in your case), the jump from one to the other will not cause any irregular space between the ticks around the boundary if you were to show many tick marks (contrary to here). All you need is to find the data point closest to the boundary to seamlessly connect both subplots, as illustrated in the following example:

    import numpy as np               # v 1.19.2
    import pandas as pd              # v 1.1.3
    import matplotlib.pyplot as plt  # v 3.3.2
    
    # Create sample dataset
    rng = np.random.default_rng(seed=1)
    x = np.linspace(4000, 400, num=50)
    y = 1 - rng.exponential(scale=0.1, size=x.size)
    df = pd.DataFrame(dict(Wavenumber=x, Intensity=y))
    
    # Select data for each subplot by using a boundary point
    x_boundary = min(df['Wavenumber'], key=lambda x: abs(x-2000))
    df1 = df[df['Wavenumber'] >= x_boundary]
    df2 = df[df['Wavenumber'] <= x_boundary]
    
    # Select x-axis ticks for each subplot
    ticks = np.array([4000, 3000, 2000, 1600, 1200, 800, 400])
    tk1 = ticks[ticks >= x_boundary]
    tk2 = ticks[ticks <= x_boundary]
    
    # Create figure with 2 Axes side-by-side with no space in between
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5), sharey=True,
                                   gridspec_kw=dict(wspace=0))
    
    # Loop through both Axes to plot data, adjust x-axis limits and remove boundary spines
    for ax, data, spine, tk in zip((ax1, ax2), (df1, df2), ('right','left'), (tk1, tk2)):
        data.plot(x='Wavenumber', xlabel='', ax=ax, legend=None)
        ax.set_xlim(data['Wavenumber'].iloc[[0,-1]])
        ax.spines[spine].set_visible(False)
        ax.set_xticks(tk)
    
    # Additional formatting
    ax2.tick_params(axis='y', length=0)
    ax1.set_xlabel('Wavenumber', x=1, labelpad=10, size=12)
    ax1.set_ylabel('Intensity', labelpad=10, size=12)
    fig.suptitle('Plot with two linear x-axis scales joined together', size=16, y=0.95);
    

    fused_scales