Python 在Matplotlib中将文本旋转到对数刻度线上 问题

Python 在Matplotlib中将文本旋转到对数刻度线上 问题,python,matplotlib,rotation,logarithm,Python,Matplotlib,Rotation,Logarithm,我正在对数刻度上处理数据,并希望将其旋转以适合直线。我知道模型,但不确定应该插入什么角度来恢复正确的旋转。经过一点尝试和错误,我知道答案是大约10度的轴限制,我需要 MWE 类似的问题()只使用线性轴,同样的问题也使用线性轴 回答评论的情节评论 在我的完整绘图中,我使用双轴(均在对数刻度上)和twinx()功能。所有数据都绘制在使用对数10标度的ax1上(如图所示)。(我可以更明确地写yscale('log',basey=10)。)。最终,我想要一个基数为10的轴 用于制作y_ols的模型

我正在对数刻度上处理数据,并希望将其旋转以适合直线。我知道模型,但不确定应该插入什么角度来恢复正确的旋转。经过一点尝试和错误,我知道答案是大约10度的轴限制,我需要

MWE

类似的问题()只使用线性轴,同样的问题也使用线性轴

回答评论的情节评论


  • 在我的完整绘图中,我使用双轴(均在对数刻度上)和
    twinx()
    功能。所有数据都绘制在使用对数10标度的ax1上(如图所示)。(我可以更明确地写
    yscale('log',basey=10)
    。)。最终,我想要一个基数为10的轴
  • 用于制作
    y_ols
    的模型来自于对一些原始数据的回归拟合,需要base-2。在对数刻度上,在任何所需的基础上恢复梯度都很容易
使用梯度 使用
np.gradient
和使用
np.arctan
的角度(以弧度为单位)的组合,在对数尺度上恢复梯度非常容易,但我似乎无法恢复接近10度(0.17弧度)的数字

给出
-1.6
弧度(大约-90度),而我需要一个接近
0.17
弧度的数字。也许我应该使用不同的基址,或者我做的都是错的(因此这篇文章)

附加-垂直偏移
从代码中可以看出,我在使用
1.2*y_ols[0]
时为锚点添加了一个垂直偏移。如果一个解决方案需要考虑这一点,那就更好了。

对于
文本框
使用哪种轴并不重要。您只需将其角度调整为
属性即可。为了更好地演示,我将对您的MWE进行一些更改。我将使用
y(x)=10^(2-x)
函数,在对数尺度上,它应该提供斜率为-45度的线性函数。如果你检查网格值,情况就是这样(函数每单位下降十年)。但是,由于体形纵横比变形,因此视角不同(两个单位只有一个正方形),需要进行调整。因此,对于给定的图形,斜率的正确值为-26.259度。请参见代码中的
调整后的坡度值

至于
文本框
的垂直偏移,您可以选择提供最佳视觉效果的值

# you can set whatever size you need
plt.figure(figsize=(6, 4))

# these are the original settings
plt.yscale('log')
plt.ylim((1e-11, 1e-1))  
plt.xlim((-0.2, 7.2))

x_fit = np.linspace(0.8, 6.2, 100)
slope = -1.0
# a slight change in the function
y_ols = (lambda x: 10**(( -2 + slope * x)))(x_fit)  

plt.plot(x_fit, y_ols, 'b-', dashes='', label='__nolegend__')

# estimate the "right" slope 
calc_slope = np.mean(np.gradient(np.log10(y_ols), np.mean(np.diff(x_fit))))

# get current figure properties
x_min, x_max = plt.xlim()
y_min, y_max = plt.ylim()
x_sz, y_sz = plt.gcf().get_size_inches()
x_factor = x_sz / (x_max - x_min)
y_factor = y_sz / (np.log10(y_max) - np.log10(y_min))  # adjust to logarithmic values 

# calculate adjustment
adjusted_slope = (calc_slope * y_factor / x_factor)  # in radians

plt.gca().text(np.min(x_fit), 1.2*y_ols[0], r'$O(10^{{ {:.3}x }})$'.format(slope),
            rotation_mode='anchor', rotation=np.arctan(adjusted_slope)*180/np.pi).set_bbox(
                dict(facecolor='w', alpha=0.7, edgecolor='k', linewidth=0)) 

注意,我提供了一个通用类来实现这一点,作为对问题的回答。这将在轴限制更改或缩放事件等方面进行自我更新,并可用于日志比例


因此,在下文中,我将仅提供线性和对数比例之间的比较,以帮助理解使用对数或线性比例与链接方法之间实际上没有任何区别

首先计算数据坐标中的角度。这可以很容易地用前两个数据(或任何其他接近数据对)的差异作为参数来完成。
然后使用将数据坐标中给定的角度转换为屏幕坐标中的角度

下面是一个例子(从另一个答案中获取数据),用于线性和对数刻度上的相同情况

import matplotlib.pyplot as plt
import numpy as np

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6, 4), sharex=True)

ax2.set_yscale('log')
ax2.set(ylim=(1e-11, 1e-1), xlim=(-0.2, 7.2))

x = np.linspace(0.8, 6.2, 100)
y = (lambda x: 10**(( -2 - x)))(x)  

# angle in data coordinates
angle_data = np.rad2deg(np.arctan2(y[1]-y[0], x[1]-x[0]))

# Apply the exact same code to linear and log axes
for ax in (ax1, ax2):

    ax.plot(x, y, 'b-')

    # angle in screen coordinates
    angle_screen = ax.transData.transform_angles(np.array((angle_data,)), 
                                              np.array([x[0], y[0]]).reshape((1, 2)))[0]

    # using `annotate` allows to specify an offset in units of points
    ax.annotate("Text", xy=(x[0],y[0]), xytext=(2,2), textcoords="offset points", 
                rotation_mode='anchor', rotation=angle_screen)

plt.show()

有几个问题需要澄清:1)您将y刻度设置为对数,但基数为10(
plt.yscale('log')
)。然后您使用的是
np.log(2)
,它的基本属性是
e
。然后你使用的是
np.log10(y_ols..
,它是以10为基数的。而且-1.6是弧度,你需要将它转换为度,以便comparison@Bazingaa,我很快会在OP中添加更多的说明。1)需要对数10刻度(在我的完整代码中,我使用双轴,一个基准10,另一个基准2,使用双轴)。2) 之所以使用
exp
log
是因为我希望base-2中模型的拟合参数(斜率和截距)与理论进行比较。3) 我认为尝试计算10进制的梯度可能是合适的,但正如OP所示,我还不清楚如何实现这一点。3) 是的,-1.6弧度,(在旋转时,我需要-0.17弧度~-10度)。我现在也给出了一个答案,它直接适用于这里。当以对数刻度计算时,但“查找我的结果”的四舍五入错误。有什么建议吗?
# you can set whatever size you need
plt.figure(figsize=(6, 4))

# these are the original settings
plt.yscale('log')
plt.ylim((1e-11, 1e-1))  
plt.xlim((-0.2, 7.2))

x_fit = np.linspace(0.8, 6.2, 100)
slope = -1.0
# a slight change in the function
y_ols = (lambda x: 10**(( -2 + slope * x)))(x_fit)  

plt.plot(x_fit, y_ols, 'b-', dashes='', label='__nolegend__')

# estimate the "right" slope 
calc_slope = np.mean(np.gradient(np.log10(y_ols), np.mean(np.diff(x_fit))))

# get current figure properties
x_min, x_max = plt.xlim()
y_min, y_max = plt.ylim()
x_sz, y_sz = plt.gcf().get_size_inches()
x_factor = x_sz / (x_max - x_min)
y_factor = y_sz / (np.log10(y_max) - np.log10(y_min))  # adjust to logarithmic values 

# calculate adjustment
adjusted_slope = (calc_slope * y_factor / x_factor)  # in radians

plt.gca().text(np.min(x_fit), 1.2*y_ols[0], r'$O(10^{{ {:.3}x }})$'.format(slope),
            rotation_mode='anchor', rotation=np.arctan(adjusted_slope)*180/np.pi).set_bbox(
                dict(facecolor='w', alpha=0.7, edgecolor='k', linewidth=0)) 
import matplotlib.pyplot as plt
import numpy as np

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6, 4), sharex=True)

ax2.set_yscale('log')
ax2.set(ylim=(1e-11, 1e-1), xlim=(-0.2, 7.2))

x = np.linspace(0.8, 6.2, 100)
y = (lambda x: 10**(( -2 - x)))(x)  

# angle in data coordinates
angle_data = np.rad2deg(np.arctan2(y[1]-y[0], x[1]-x[0]))

# Apply the exact same code to linear and log axes
for ax in (ax1, ax2):

    ax.plot(x, y, 'b-')

    # angle in screen coordinates
    angle_screen = ax.transData.transform_angles(np.array((angle_data,)), 
                                              np.array([x[0], y[0]]).reshape((1, 2)))[0]

    # using `annotate` allows to specify an offset in units of points
    ax.annotate("Text", xy=(x[0],y[0]), xytext=(2,2), textcoords="offset points", 
                rotation_mode='anchor', rotation=angle_screen)

plt.show()