在本文中,我们将更多注意力放在展现的数据所表达的含义上,以及如何通过图表把它有效地表达出来。我们将展示一些新的技术和图表,当知道想要传达给用户什么信息后,我们对这些图表的理解会更深刻。有这样的一个问题:“为什么要以这种方式展示数据?”这在数据探索阶段是最重要的一个问题。如果没能很好地理解数据就把它以某种形式展示出来,那么毫无疑问,读者也将难以正确地理解这些数据。
1.1 理解对数图
很多情况下,在读日报及类似的文章时,人们常常发现媒体机构用图表歪曲了事实。一个常见的例子是用线性标度来创建所谓的恐慌图。图表中有一个在很长一段时间(若干年)内持续增长的值,其起始值要比最新的值小好几个量级。然而在正确的可视化时,这些值可以(并且通常应该)用线形图或者近似线性的图表表示,把它们要强调的一些恐慌因素忽略。
1.1.1 准备工作
使用对数标度时,连续值的比例是常量。这在读对数图表时是非常重要的。使用线性(算术)标度时,连续值之间的距离是常量。换句话说,对数图表按数量级顺序有一个常量的距离。这在接下来的图表中可以看到,生成图表的代码在后面也会解释。
根据一般经验,遇到以下情况应该使用对数标度。
当要展示的数据的值跨越好几个量级时。
当要展示的数据有朝向大值(一些数据点比其他数据大很多)的倾斜度时。
当要展示变化率(增长率),而不是值的变化时。
不要盲目地遵循这些规则,它们更像是指导,而不是规则,要始终依靠你自己对于手头的数据和项目,或者客户对你提出的需求作判断。
根据数据范围的不同,我们应该使用不同的对数底。对数的标准底是10,但是如果数据范围比较小,以2为底数会好一些,因为其会在一个较小的数据范围下有更多的分辨率。
如果有适合在对数标度上显示的数据范围,我们会注意到,以前非常靠近而难以判断差异的值现在很好地区分开了。这让我们很容易读懂原来在线性标度下难以理解的数据。
对于长时间范围的数据的增长率图表,我们想看的不是在时间点所测量的绝对值,而是其在时间上的增长。虽然我们仍可以得到绝对值信息,但是这些信息的优先级较低。
再者,如果数据分布存在一个正偏态,例如工资,取值(工资)的对数能让数据更合乎模型,因为对数变换能提供一个更加正常的数据分布。
1.1.2 操作步骤
我们将用一段代码来证明上面所述的内容。这段代码用不同的标度(线性和对数)在两个不同的图表中显示了两个相同的数据集合(一个线性的,一个对数的)。
我们将借助后面的代码实现下面的步骤。
(1)生成两个简单的数据集合:指数/对数y和线性z。
(2)创建一个包含4个子区的图形。
(3)创建两个包含数据集合y的子区:一个为对数标度,一个为线性标度。
(4)创建两个包含数据集合z的子区:一个为对数标度,一个为线性标度。
代码如下:
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(1, 10)
y = [10 ** el for el in x]
z = [2 * el for el in x]
fig = plt.figure(figsize=(10, 8))
ax1 = fig.add_subplot(2, 2, 1)
ax1.plot(x, y, color='blue')
ax1.set_yscale('log')
ax1.set_title(r'Logarithmic plot of $ {10}^{x} $ ')
ax1.set_ylabel(r'$ {y} = {10}^{x} $')
plt.grid(b=True, which='both', axis='both')
ax2 = fig.add_subplot(2, 2, 2)
ax2.plot(x, y, color='red')
ax2.set_yscale('linear')
ax2.set_title(r'Linear plot of $ {10}^{x} $ ')
ax2.set_ylabel(r'$ {y} = {10}^{x} $')
plt.grid(b=True, which='both', axis='both')
ax3 = fig.add_subplot(2, 2, 3)
ax3.plot(x, z, color='green')
ax3.set_yscale('log')
ax3.set_title(r'Logarithmic plot of $ {2}*{x} $ ')
ax3.set_ylabel(r'$ {y} = {2}*{x} $')
plt.grid(b=True, which='both', axis='both')
ax4 = fig.add_subplot(2, 2, 4)
ax4.plot(x, z, color='magenta')
ax4.set_yscale('linear')
ax4.set_title(r'Linear plot of $ {2}*{x} $ ')
ax4.set_ylabel(r'$ {y} = {2}*{x} $')
plt.grid(b=True, which='both', axis='both')
plt.show()
代码将生成图1-1所示的图表。
图1-1
1.1.3 工作原理
我们生成一些样本数据和两个相关的变量:y和z。变量y被表示为数据x的指数函数,变量z是x的简单线性函数。这展示了线性图表和指数图表的区别。
然后创建4个子区,上面一行子区是关于数据(x,y)的,下面一行子区是关于数据(x,z)的。
从左手边看,y轴列为对数标度;从右手边看,y轴列为线性标度。通过set_yscale('log')分别对每一个坐标轴进行设置。
我们为每一个子区设置标题和标签,标签描述了所绘制的函数。
通过plt.grid(b=True, which='both', axis='both'),我们为每个图的两个坐标轴和主次刻度打开网格显示。
我们观察到,在线性图表中线性函数是直线,在对数图表中对数函数也是直线。
1.2 理解频谱图
频谱图是随时间变化的频谱表现,它显示了信号的频谱强度随时间的变化。
频谱图是把声音或者其他信号的频谱以可视化的方式呈现出来。它被用在很多科学领域中,从声音指纹如声音识别,到雷达工程学和地震学。
通常,频谱图的布局如下:x轴表示时间,y轴表示频率,第三个维度是频率—时间对的幅值,通过颜色表示。因为这是三维的数据,因此我们也可以创建3D图表来表示,其中强度表示为z轴上的高度。3D图表的问题是人们不太容易理解以及进行比较,而且它比2D图表占用更多的空间。
1.2.1 准备工作
对于严谨的信号处理,我们将会研究更低级别的细节,进而能从中发现模式并自动识别一定的特征。但是对于本节数据可视化的内容,我们将借助一些著名的Python库来读取音频文件,对它进行采样,然后绘制出频谱图。
为了能读取WAV文件并把声音可视化出来,需要做一些准备工作。我们需要安装libsndfile1系统库来读/写音频文件。这可以通过你喜欢的包管理工具完成。对于Ubuntu,使用以下命令:
$ sudo apt-get install libsndfilel-dev.
安装dev包非常重要,它包含了头文件,从而通过pip可以创建scikits.audiolab模块。
我们也可以安装libasound和ALSA(Advanced Linux Sound Architecture,高级Linux声音体系)头来避免编译时警告。这是可选的,因为我们不打算使用ALSA库提供的特性。对于Ubuntu Linux,执行以下命令:
$ sudo apt-get install libasound2-dev
我们用pip安装用来读取WAV文件的scikits.audiolab:
$ pip install scikits.audiolab
1.2.2 操作步骤
本节将使用预录制的声音文件test.wav,该文件可以在本书的文件代码库中找到,但你也可以自己生成一个样本文件。
在这个例子中,我们顺序地执行下面的步骤。
(1)读取包含一个已经录制的声音样本的WAV文件。
(2)通过NFFT设置用于傅里叶变换的窗口长度。
(3)在采样时,使用noverlap设置重叠的数据点。
import os
from math import floor, log
from scikits.audiolab import Sndfile
import numpy as np
from matplotlib import pyplot as plt
# Load the sound file in Sndfile instance
soundfile = Sndfile("test.wav")
# define start/stop seconds and compute start/stop frames
start_sec = 0
stop_sec = 5
start_frame = start_sec * soundfile.samplerate
stop_frame = stop_sec * soundfile.samplerate
# go to the start frame of the sound object
soundfile.seek(start_frame)
# read number of frames from start to stop
delta_frames = stop_frame - start_frame
sample = soundfile.read_frames(delta_frames)
map = 'CMRmap'
fig = plt.figure(figsize=(10, 6), )
ax = fig.add_subplot(111)
# define number of data points for FT
NFFT = 128
# define number of data points to overlap for each block
noverlap = 65
pxx, freq, t, cax = ax.specgram(sample, Fs=soundfile.samplerate,
NFFT=NFFT, noverlap=noverlap,
cmap=plt.get_cmap(map))
plt.colorbar(cax)
plt.xlabel("Times [sec]")
plt.ylabel("Frequency [Hz]")
plt.show()
1.2.3 工作原理
首先需要加载一个声音文件,这通过调用scikits.audiolab.SndFile方法并传入一个文件名来完成。该方法将实例化一个声音对象,通过该对象我们可以查询数据并调用其中的方法。
为了读取频谱图所需要的数据,需要从声音对象中读取数据帧。这通过read_frames()完成,该方法接收开始帧和结束帧的参数。把采样率和想要可视化的时间点(start, end)相乘便可以计算出帧数量。
1.2.4 补充说明
如果找不到音频文件(wave),可以生成一个。生成方法很简单,具体如下。
import numpy
def _get_mask(t, t1, t2, lvl_pos, lvl_neg):
if t1 >= t2:
raise ValueError("t1 must be less than t2")
return numpy.where(numpy.logical_and(t > t1, t < t2), lvl_pos,
lvl_neg)
def generate_signal(t):
sin1 = numpy.sin(2 * numpy.pi * 100 * t)
sin2 = 2 * numpy.sin(2 * numpy.pi * 200 * t)
# add interval of high pitched signal
sin2 = sin2 * _get_mask(t,2,5,1.0,0.0)
noise = 0.02 * numpy.random.randn(len(t))
final_signal = sin1 sin2 noise
return final_signal
if __name__ == '__main__':
step = 0.001
sampling_freq=1000
t = numpy.arange(0.0, 20.0, step)
y = generate_signal(t)
# we can visualize this now
# in time
ax1 = plt.subplot(211)
plt.plot(t, y)
# and in frequency
plt.subplot(212)
plt.specgram(y, NFFT=1024, noverlap=900,
Fs=sampling_freq, cmap=plt.cm.gist_heat)
plt.show()
这将生成图1-2所示的信号,其中顶部的图形是生成的信号。这里,x轴表示时间,y轴表示信号的幅值。底部的图形是相同的信号在频率域中的呈现。这里,x轴如顶部图一样表示时间(通过选择采样率来匹配时间),y轴表示信号的频率。
图1-2