如何用Altair绘制频谱图?DataFrame格式适配与轴编码问题
使用Altair绘制频谱图的解决方案
问题背景
现有一个宽格式DataFrame:列名为频率区间,索引为时间/帧,单元格值对应对应时间下该频率的幅值。需要用Altair绘制频谱图,将频率作为Y轴、时间作为X轴、幅值作为颜色编码,但不清楚如何让Altair识别列名作为Y轴,或是否需要重构数据。已有Matplotlib版本实现,需要Altair的交互功能。
数据集示例
col_freq= [172.265625, 344.53125, 516.796875, 689.0625] row_1 = [1610974057651.0325, 1261973870532.6091, 234137860730.91223, 42549716015.37] row_2 = [4741489056189.282, 3278778293422.225, 160494114891.44345, 57040784835.97968] row_3 = [198776867252.5261, 661886049124.3528, 188309227047.4264, 124549622810.97015] data = [row_1, row_2, row_3] df = pd.DataFrame(data, columns=col_freq)
原Scipy数据生成代码
import pandas as pd import altair as alt import scipy as sc from scipy import fft, signal from matplotlib import pyplot as plt # 替换为你的音频文件路径 sample_rate, audio_file = sc.io.wavfile.read('path/my_audio.wav') size = 2048 window_func = signal.windows.hann(size) def wave_to_spect(x, label): freq, time, Sxx = signal.spectrogram(x, sample_rate, window=window_func, nperseg=len(window_func)) col_name = [str(x) for x in freq] df = pd.DataFrame(Sxx, index=col_name) df = df.T # Matplotlib可视化 plt.pcolormesh(time, freq, np.log(Sxx), shading='gouraud') plt.title('Spectrogram ' + label) plt.ylabel('Frequency [Hz]') plt.xlabel('Time [sec]') plt.show() wave_to_spect(audio_file, "audio_car")
核心解决方案:重构为长格式数据
Altair基于Vega-Lite,更适配长格式(long-form)数据——即每行对应一个观测值,列对应时间、频率、幅值三个变量。当前宽格式数据需要先转换。
步骤1:修改数据生成逻辑,直接输出长格式DataFrame
调整wave_to_spect函数,生成符合Altair要求的结构:
import numpy as np def wave_to_spect_altair(x, label): freq, time, Sxx = signal.spectrogram(x, sample_rate, window=window_func, nperseg=len(window_func)) # 生成长格式数据:重复时间、平铺频率、展平幅值 df_long = pd.DataFrame({ 'time': np.repeat(time, len(freq)), 'frequency': np.tile(freq, len(time)), 'amplitude': np.log(Sxx).flatten() # 和Matplotlib保持对数处理 }) return df_long, label
步骤2:用Altair绘制交互式频谱图
直接映射变量到编码通道,启用交互功能:
# 获取长格式数据 df_spect, plot_label = wave_to_spect_altair(audio_file, "audio_car") # 绘制频谱图 chart = alt.Chart(df_spect).mark_rect().encode( x=alt.X('time:Q', title='Time [sec]'), y=alt.Y('frequency:Q', title='Frequency [Hz]'), color=alt.Color('amplitude:Q', title='Log Amplitude', scale=alt.Scale(scheme='viridis')), # 可选颜色方案 tooltip=['time:Q', 'frequency:Q', 'amplitude:Q'] # 鼠标悬停显示详情 ).properties( title=f'Spectrogram - {plot_label}', width=800, height=400 ).interactive() # 启用缩放、平移交互 chart.show()
已有宽格式DataFrame的转换方法
如果已经存在宽格式数据,用pd.melt快速转换:
# 将时间索引转为列 df.reset_index(inplace=True, names='time') # 宽转长 df_long = df.melt( id_vars='time', var_name='frequency', value_name='amplitude' ) # 频率列转为数值类型(若原列名为字符串) df_long['frequency'] = pd.to_numeric(df_long['frequency']) # 可选:对幅值取对数 df_long['amplitude'] = np.log(df_long['amplitude'])
额外优化建议
- 颜色方案:替换
scheme参数使用不同配色,比如inferno、magma - 性能优化:大数据量下启用
alt.data_transformers.enable('vegafusion')提升渲染速度 - 交互增强:添加选择器(如
alt.selection_interval())实现频率/时间范围过滤
内容的提问来源于stack exchange,提问作者phidelity
相关产品推荐
相关产品推荐

