首页 > 解决方案 > log1p 是对图表进行对数比例转换的“正确”方式吗?

问题描述

在将数据转换为对数比例以用于图表目的时,在某种程度上总是转换使用比使用更“正确”吗?它是否打破了任何常见的用户期望?np.log1pnp.log

我正在构建一个具有对数刻度功能的图表软件,并且想知道在转换数据时是否应该使用np.lognp.log1p作为默认选择。

这是一个大大简化的代码示例:

import matplotlib.pyplot as plt
def chart_with_log_scale(x,y):
  ylog = np.log(y) # should I be using np.log1p here instead?
  plt.scatter(x,ylog )

或者对此有不同的看法,是否matplotlib使用log1plog何时在这样的代码中进行日志转换?

def chart_with_log_scale2(x,y):
  plt.scatter(x,y)
  ax = plt.gca()
  ax.set_yscale("log")

标签: pythonnumpymatplotlibdata-visualizationfloating-accuracy

解决方案


在将数据转换为对数刻度以用于制图时,在某种程度上总是转换使用np.log1p比使用更“正确”np.log吗?它是否打破了任何常见的用户期望?

如果您的目标是计算 log() np.log1p,那么使用它几乎是不正确的。np.log

以下是 y 轴为对数刻度的绘图示例,其中 Beta 分布的概率密度函数为 = 2 和 = 5:

Beta(2,5) 的日志 PDF

这是与 log1p 比例中的 y 轴相同的函数:

log 1 + PDF of Beta(2,5)

如果我作为一名研究生试图将其作为 Beta(2,5) PDF 的对数比例图传递出去,我的导师可能会当场将我射死。

(例外:如果您的输入在具有 IEEE 754 binary64 算术的机器上总是大于 2 53,那么这两个函数很可能会重合。但这仅仅是因为 log(1 + ) 与 log() 的相对误差如此之低在这样的输入上——即 |log(1 + ) − log()|/|log()| = |log(⋅(1/ + 1)) − log()|/log() = log(1 + 1/)/log() < 1/ < 2 −53所以 log(1 + ) 在最坏的情况下是远离 log() 的舍入误差。)


在评论中,您问:

如果值非常接近 0,log1p 可能是我想要的,因为它比 log 具有更好的数值稳定性,对吧?

函数log1p和 log 只是数学函数。两者都没有比另一个“更好的数值稳定性”:“数值稳定性”甚至不是一个定义明确的概念,当然也不是数学函数。计算数学函数的算法可以表现出前向或后向稳定性;该属性的含义与它旨在计算的功能有关。但 log 和 log1p 只是数学函数,不是计算函数的算法,因此,前向和后向稳定性不适用。

log1p 的重要性在于函数log(1 + ) 在零附近是良条件的,并且经常出现在数值算法或其他函数的代数重排中。 条件良好的意思是,如果你在 ⋅(1 + ) 点计算它,而实际上你想在 计算它,那么答案 log(1 + ⋅(1 + )) 等于 log(1 + )⋅(1 + ) 其中是只要合理小的倍数。这里是输入 ⋅(1 + ) 从 的相对误差,是输出 log(1 + )⋅(1 + ) 从 log() 的相对误差。

相比之下,函数 log()在 1 附近是病态的:当你想要 log() 在 1 附近的某个点时评估 log(⋅(1 + )),你得到的可能是 log()⋅(1 + ) 对于任意错误错误,即使输入错误非常小。例如,假设您要计算 log(1.000000000000001) ≈ 9.999999999999995 × 10 -16。如果你np.log(1.000000000000001)在 Python 程序中编写,十进制常量1.000000000000001将四舍五入到最接近的 binary64 浮点数,因此你将实际计算 log(fl(1.000000000000001)) = log(1.0000000000000011102230246251565404236316680908203125) ≈ 651022302 ≈ 651022302 ≈ 651022302

Although 1.0000000000000011102230246251565404236316680908203125 is a good approximation to 1.000000000000001, with relative error < 10 −15 , log(1.0000000000000011102230246251565404236316680908203125) is a terrible approximation to log(1.000000000000001), with relative error > 11%. 这不是问题,它在将正确舍入的结果返回给我们提出np.log的问题方面做得非常出色。这是因为数学函数log 在 1 附近是病态的,所以它从我们想要询问的输入中放大了我们询问的输入中的微小误差 10 -15 ——而且不仅被放大了,而且被放大了千万亿倍!

因此,如果您发现自己拥有一个很小的实数,并且您想知道 log(1 + ) 是什么,那么您应该用它np.log1p(x)来回答这个问题。 (或者您可能希望根据 log(...) 重新安排计算,以便它使用 log(1 + ...) 代替;例如,为给定的接近 1/ 计算 logit() = log(/(1 - )) 2 ,最好将它重写为 log(1 + (1 − 2)/ np.log(1 + x)) 。 )⋅(1 + )。尽管舍入误差很小(在 binary64 算术中,可以保证 || ≤ 2 −53),但 log函数可能会在输出中将其放大为任意大的误差。np.log1p(x)1 + x

但是如果你已经有一个数字,即使它接近于零,并且发现自己想要 log(),那么np.log(y)会给 log() 一个很好的近似值,并且np.log1p(y)会给出一个糟糕的近似值(除非它非常大)。 这就是你似乎发现自己所处的场景。

在对数刻度上绘制数据时可能 np.log1p会相关吗?也许,如果您计算的内容和您希望绘制的内容是对数刻度的 1 +。但是这种情况的组合——计算 ,并以对数比例绘制 1 + ——不太可能一起有意义:

  • 如果您有充分的理由将计算作为 1 + 的代理,则很可能您主要关心接近零的值 - 否则表示没有太多好处 - 因此很可能您正在绘制接近 1 的 1 + 值。
  • 但是,如果您绘制 1 + 接近 1 的值,则几乎没有理由使用对数刻度,因为您的数据点越接近 1,对数刻度和线性刻度之间的差异就越小!

对数刻度 gnuplot

set terminal pngcairo
set output "logscale.png"
set title 'log scale'
set xrange [0:1]
set logscale y
plot x**(2 - 1) * (1 - x)**(5 - 1) notitle

log1p 比例 gnuplot

set terminal pngcairo                            
set output "log1pscale.png"
set title 'log1p scale'
set xrange [0:1]
set yrange [1:1.1]
set logscale y 2
set ytics 1.1**(1/4.0)
plot 1 + x**(2 - 1) * (1 - x)**(5 - 1) notitle

推荐阅读