标尺和变换
某些数据分布可以进行标尺变换。最常见的示例是近似符合对数正态分布的数据。即采用原始单位的话,看起来非常偏态:很多数据点的值很小,有一个很长的尾部,尾部数据点的值很大。但是对这些值取对数的话,数据看起来是正态分布的。
plt.figure(figsize = [10, 5])
# histogram on left: natural units
plt.subplot(1, 2, 1)
bin_edges = np.arange(0, ln_data.max()+100, 100)
plt.hist(ln_data, bins = bin_edges)
# histogram on right: directly log-transform data
plt.subplot(1, 2, 2)
log_ln_data = np.log10(ln_data)
log_bin_edges = np.arange(0.8, log_ln_data.max()+0.1, 0.1)
plt.hist(log_ln_data, bins = log_bin_edges)
plt.xlabel('log(values)') # add axis label for clarity
(文档numpy log10
, matplotlib xlabel
)
在左图中,超过 1000 的很高的值将大部分数据点推到了最左侧的分箱中。对于右图,对数变换将这些数据点看起来和剩余的数据点保持一致,整个数据看起来是单峰的。右图的最大问题是 x 轴的单位很难解释:对于很多人来说,他们只会相对娴熟地将整数的对数值转换为原始值(假设示例中的基数是 如10 等相对比较好算的值)。
这时候标尺变换就派上用场了。在标尺变换中,值之间的空隙基于变换的标尺,但是你可以用变量的原始单位解释数据。此外,你不需要设定新的特征,这很方便。Matplotlib 的 xscale
函数包含几个内置的变换:我们将使用 "对数" 标尺。
bin_edges = np.arange(0, ln_data.max()+100, 100)
plt.hist(ln_data, bins = bin_edges)
plt.xscale('log')
image.png
对于该图,注意两点:首先,即使数据采用的是对数标尺,分箱依然呈线性分布。意味着它们的尺寸从左到右由宽变窄,因为值会以倍数增大。其次,默认的标签设置依然很难解释,并且是稀疏的。
要处理分箱尺寸问题,我们只需将它们变成 10 的各次幂并且均匀分布。根据你所绘制的数据,2 次幂等其他次幂可能更合适。对于刻度,我们可以使用 xticks
以原始单位指定位置和标签。注意:我们并没有更改数据的值,只是改变了显示方式。在 10 次幂的整数之间,我们没有表示均匀刻度的整数,但是可以很接近。对于 10 次幂对数变换,设置 1-3-10 或 1-2-5-10 这样的循环刻度很有用。
bin_edges = 10 ** np.arange(0.8, np.log10(ln_data.max())+0.1, 0.1)
plt.hist(ln_data, bins = bin_edges)
plt.xscale('log')
tick_locs = [10, 30, 100, 300, 1000, 3000]
plt.xticks(tick_locs, tick_locs)
请务必在 xscale
之后指定 xticks
,因为该函数具有内置的刻度设置。
我们获得了和进行直接对数变换时得出的图形一样的图形,但是现在的刻度和标签看起来美观多了。
替代方法
注意,对数变换并不是唯一的变换方式。在进行对数变换时,数据值必须全是正数;0 或负数无法取对数。此外,对数变换表明对对数标尺进行加法将导致原始标尺出现倍数变化,这是在数据建模时需要注意的重要事项。你可以根据数据判断该选择什么类型的变换。例如,这篇维基百科文章的此部分介绍了几个运用对数正态分布的示例场合。
如果你想使用 xscale
中未提供的其他变换,则需要进行某些特征工程。在这种情形下,我们需要写一个应用变换和还原过程的函数,以保持系统性。当我们用变换单位指定值,并且需要获得以原始单位计量的值时,还原功能就很有用。为了进行演示,假设我们想要以平方根变换的形式绘制上述数据。(或许这些数字表示面积,我们认为有必要按照半径、长度或其他一维近似值来对数据建模)。我们可以如下所示地绘制变换后的分布情况:
def sqrt_trans(x, inverse = False):
""" transformation helper function """
if not inverse:
return np.sqrt(x)
else:
return x ** 2
bin_edges = np.arange(0, sqrt_trans(ln_data.max())+1, 1)
plt.hist(ln_data.apply(sqrt_trans), bins = bin_edges)
tick_locs = np.arange(0, sqrt_trans(ln_data.max())+10, 10)
plt.xticks(tick_locs, sqrt_trans(tick_locs, inverse = True).astype(int))
注意 ln_data
是一个 pandas Series,因此我们可以使用该函数的 apply
方法。如果是 NumPy 数组,则需要像在其他情形下一样应用该函数。刻度位置也应该用原始值指定,我们对 xticks
的第一个参数应用标准变换函数。
网友评论