在本文中,我們將更多注意力放在展現的數據所表達的含義上,以及如何通過圖表把它有效地表達出來。我們將展示一些新的技術和圖表,當知道想要傳達給用戶什麼信息後,我們對這些圖表的理解會更深刻。有這樣的一個問題:“為什麼要以這種方式展示數據?”這在數據探索階段是最重要的一個問題。如果沒能很好地理解數據就把它以某種形式展示出來,那麼毫無疑問,讀者也將難以正確地理解這些數據。
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
閱讀更多 異步社區 的文章