在 matplotlib 上缩放两个不同的 y 轴的问题

Problem with scaling two different y-axis on matplotlib

我想在一个 x 轴和两个 y 轴(eVnm)上绘制数据集。两个 y 轴与等式链接在一起:nm = 1239.8/eV.

正如您从我的图片输出中看到的那样,值的位置不正确。例如,在 eV = 0.5 我需要 nm = 2479.6,在 eV = 2.9nm = 423,等等……

我该如何解决这个问题?

我的data.txt:

number eV nm
1 2.573 481.9
2 2.925 423.9
3 3.174 390.7
4 3.242 382.4
5 3.387 366.1

我使用的代码:

#!/usr/bin/env python3

import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.ticker as tck

# data handling

file = "data.txt"

df = pd.read_csv(file, delimiter=" ")  # generate a DataFrame with data

no = df[df.columns[0]]
eV = df[df.columns[1]].round(2)  # first y-axis
nm = df[df.columns[2]].round(1)  # second y-axis

# generate a subplot 1x1

fig, ax1 = plt.subplots(1,1)

# first Axes object, main plot (lollipop plot)

ax1.stem(no, eV, markerfmt=' ', basefmt=" ", linefmt='blue', label="Gas")

ax1.set_ylim(0.5,4) 
ax1.yaxis.set_minor_locator(tck.MultipleLocator(0.5))
ax1.set_xlabel('Aggregation', labelpad=12)
ax1.set_ylabel('Transition energy [eV]', labelpad=12)

# adding second y-axis

ax2 = ax1.twinx()
ax2.set_ylim(2680,350)  # set the corresponding ymax and ymin, 
                        # but the values are not correct anyway
ax2.set_yticklabels(nm)
ax2.set_ylabel('Wavelength [nm]', labelpad=12)

# save

plt.tight_layout(pad=1.5)
plt.show()

结果图如下。我只想用第一个轴除以 1239.8 得到第二个轴,我不知道还要找什么!

您可以使用 ax.secondary_yaxis,如 this example. 中所述,请参阅下面的代码来解决您的问题。我只包含了与第二个 y 轴相关的部分代码。

# adding second y-axis
def eV_to_nm(eV):
    return 1239.8 / eV

def nm_to_eV(nm):
    return 1239.8 / nm

ax2 = ax1.secondary_yaxis('right', functions=(eV_to_nm, nm_to_eV))
ax2.set_yticks(nm)
ax2.set_ylabel('Wavelength [nm]', labelpad=12)

请注意,我也使用 set_yticks 而不是 set_yticklabels。此外,如果您删除 set_yticks,matplotlib 将自动确定 y 刻度位置,假设 y 刻度线呈线性分布。然而,因为 nmeV 成反比,这将导致(最有可能)不理想的 y 刻度分布。您可以使用 set_yticks.

中的一组不同的值手动更改这些

我想出了解决这个问题的方法 (source of the hint here)。

因此,对于任何需要具有 一个 x 轴但两个 y 轴 (一个在数学上与另一个相关)的数据集的人,报告了一个可行的解决方案。基本上,问题是具有与主 y 轴相同的刻度,但根据它们的数学关系(即,在本例中为 nm = 1239.8/eV)按比例更改它们。以下代码已经过测试并且可以正常工作。

如果您有两个 x 轴和 1 个共享 y 轴等,则此方法当然有效。

重要说明:您必须定义一个 y 范围(如果您想要相反的结果,则可以定义 x 范围),否则您可能会遇到一些缩放问题。

#!/usr/bin/env python3

import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.ticker as tck
from matplotlib.text import Text

# data

file = "data.txt"

df = pd.read_csv(file, delimiter=" ")  # generate a DataFrame with data

no = df[df.columns[0]]
eV = df[df.columns[1]].round(2)  # first y-axis
nm = df[df.columns[2]].round(1)  # second y-axis

# generate a subplot 1x1

fig, ax1 = plt.subplots(1,1)

# first Axes object, main plot (lollipop plot)

ax1.stem(no, eV, markerfmt=' ', basefmt=" ", linefmt='blue', label="Gas")

ax1.set_ylim(0.5,4) 
ax1.yaxis.set_minor_locator(tck.MultipleLocator(0.5))
ax1.set_xlabel('Aggregation', labelpad=12)
ax1.set_ylabel('Transition energy [eV]', labelpad=12)

# function that correlates the two y-axes

def eV_to_nm(eV):
    return 1239.8 / eV

# adding a second y-axis

ax2 = ax1.twinx()  # share x axis
ax2.set_ylim(ax1.get_ylim())  # set the same range over y
ax2.set_yticks(ax1.get_yticks())  # put the same ticks as ax1
ax2.set_ylabel('Wavelength [nm]', labelpad=12)

# change the labels of the second axis by apply the mathematical 
# function that relates the two axis to each tick of the first
# axis, and then convert it to text
# This way you have the same axis as y1 but with the same ticks scaled

ax2.set_yticklabels([Text(0, yval, f'{eV_to_nm(yval):.1f}') 
                     for yval in ax1.get_yticks()])

# show the plot

plt.tight_layout(pad=1.5)
plt.show()

data.txt同上:

number eV nm
1 2.573 481.9
2 2.925 423.9
3 3.174 390.7
4 3.242 382.4
5 3.387 366.1

Output image here