精彩,Matplotlib 高级绘图永不过时!
在数据驱动决策的时代,有效可视化不仅是展示成果的手段,更是推动科学认知的关键桥梁。Matplotlib作为Python生态中最强大的可视化工具之一,能够将复杂数据转化为直观见解——无论是追踪全球疫情趋势的COVID-19热力图,还是揭示微观规律的散点矩阵。
本文中云朵君将以真实疫情数据为脉络,带您掌握从基础折线图到三维投影的完整可视化方法论,让你的数据不仅被看见,更能被理解、被记住。
1. 日历热图
我在这里使用的第一个图表是日历热图。这种类型的图表经常出现在医院仪表盘甚至咖啡馆的分析中。但将其用于科学数据对我来说是一个全新的挑战。
当我输入 COVID-19 的数据时,你可以清楚地看到病人数量随着时间的推移而增加。如果你有任何与日常趋势相关的数据集,这个图表就非常适合你的仪表盘。
说到我们是如何做到这一点的,Seaborn起到了关键作用。它提供了许多内置功能,如方形块、每周分组和颜色渐变。在典型的热图中,浅色表示正常水平,深红色反映严重程度。
如果查看 Seaborn 的官方文档,会发现有很多自定义选项,可以让热图更有意义。
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
df = pd.read_csv('covid_cases.csv')
df['Date'] = pd.to_datetime(df['Date'])
df['Day'] = df['Date'].dt.day
df['Weekday'] = df['Date'].dt.weekday
df['Month'] = df['Date'].dt.month
df_month = df[df['Month'] == 12]
heatmap_data = pd.pivot_table(
df_month, values='New_Cases', index='Weekday', columns='Day', aggfunc=np.sum
)
heatmap_data = heatmap_data.reindex([0, 1, 2, 3, 4, 5, 6])
heatmap_data.index = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
plt.figure(figsize=(12, 4))
sns.heatmap(heatmap_data, cmap='Reds', linewidths=0.5, linecolor='gray', annot=True, fmt=".0f")
plt.title('COVID-19 Daily New Cases - December Calendar Heatmap')
plt.xlabel('Day of Month')
plt.ylabel('Weekday')
plt.tight_layout()
plt.show()
2. Calmap 时间序列图
研究人员可以考虑使用的另一种图表是Calmap 时间序列图。虽然它通常用于显示生产力模式,但它在科学数据方面也有巨大潜力。
当我把 COVID-19 的数据输入其中,看看它的表现如何时,结果出人意料。折线上的条虚线清楚地表明了下一个转折点,COVID-19案例的个上升趋势精确地显示出来。
import pandas as pd
import matplotlib.pyplot as plt
import calmap
# 加载 CSV
df = pd.read_csv('covid_cases.csv')
df['Date'] = pd.to_datetime(df['Date'])
# 转换为 calmap 的系列(索引必须是日期时间,值 = 案例)
cases_series = pd.Series(df['New_Cases'].values, index=df['Date'])
# 创建年度日历热图
plt.figure(figsize=(16, 4))
calmap.calendarplot(cases_series, cmap='Reds', fillcolor='lightgray', linewidth=0.5, fig_kws=dict(figsize=(16, 4)))
plt.title('Calendar Heatmap of COVID-19 Daily New Cases')
plt.tight_layout()
plt.show()
1. X 轴(水平)——日期
显示从 2020–12–01 到 2020–12–31 的日期。 轴向右倾斜,以便更好地读取日期。
2. Y 轴(垂直)——新病例
标记为“新病例”。 显示每日新增 COVID-19 病例数。 范围从 1000到4500+ 。
3. 无残差图
残差图是最科学的图表之一,因为它揭示了其他图表通常无法揭示的问题--预测中的误差。
简单地说,它是实际值与预测值之间差异的图表:
残差 = 实际值 − 预测值
这有助于您了解模型在哪些方面表现良好,在哪些方面出现偏差,为提高准确性提供有价值的见解。
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LinearRegression
# 加载数据
df = pd.read_csv('covid_cases.csv')
df['Date'] = pd.to_datetime(df['Date'])
# 将日期转换为数字格式(自第一个日期以来的天数)
df['Day_Num'] = (df['Date'] - df['Date'].min()).dt.days
# 准备 X 和 y 进行回归
X = df[['Day_Num']] # 自变量
y = df['New_Cases'] # 因变量
# 拟合简单的线性回归模型
model = LinearRegression()
model.fit(X, y)
predictions = model.predict(X)
residuals = y - predictions
# 我们在这里使用 Seaborn 主要是为了可视化——它使绘图更容易、更漂亮,代码更少。
plt.figure(figsize=(10, 5))
sns.residplot(x=predictions, y=residuals, lowess=True, color='purple')
plt.axhline(0, linestyle='--', color='gray')
plt.title('Residual Plot - COVID-19 Daily Case Predictions')
plt.xlabel('Predicted Cases')
plt.ylabel('Residuals')
plt.tight_layout()
plt.show()
当虚线接近实际线时,说明你的预测是准确的--这表示为 残差 = 0。
同理:
距离越小=预测越好 距离越大=误差越大 实际线以上的线表示病例预测不足 低于实际线表示对案例的预测过高。
最重要的是,所有这一切都要归功于Scikit-learn。
Scikit-learn 是一个 Python 库,它是一个自学习模块,可以通过分析过去的趋势来预测未来的数值。例如,对于 COVID-19 数据,当我使用 Scikit-learn 时,它会分析 10 月和 11 月的趋势来预测 12 月的情况。通过残差图,我可以清楚地看到这些预测的准确性。
4. Scikit-learn 箭头图
箭头图通常用于可视化矢量场——它使用箭头表示方向和幅度。虽然它在物理学或工程学中很常见,但我们在这里展示了它在数据分析中也能体现科学性和意义。
在我们的图中:
x 轴表示新增COVID-19 病例数 y 轴表示2020年 12 月的天数
现在你可能会问——难道我们不能用系列图来实现这个效果吗? 是的,可以。但这就是为什么在这种情况下使用箭头图更有意义:
这些尖锐的箭头代表每日变化率——病例数量的增加或减少速度。 这个关键细节是序列图无法清晰显示的。
虽然我们也介绍了序列图,但箭头图为我们提供了数据快速变化的更清晰的视觉提示。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
# 加载数据
df = pd.read_csv('covid_cases.csv')
df['Date'] = pd.to_datetime(df['Date'])
df['Day_Num'] = (df['Date'] - df['Date'].min()).dt.days
df = df.sort_values('Date')
# 计算梯度(病例随时间的变化)
X = df['Day_Num'].values.reshape(-1, 1)
y = df['New_Cases'].values
model = LinearRegression().fit(X, y)
trend = model.predict(X)
# 箭头分量:位置和增量
x = df['Day_Num'].values[:-1]
y_pos = df['New_Cases'].values[:-1]
u = df['Day_Num'].diff().fillna(0).values[1:] # 水平方向(通常为 1)
v = df['New_Cases'].diff().fillna(0).values[1:] # 垂直变化
# 绘图
plt.figure(figsize=(12, 5))
plt.quiver(x, y_pos, u, v, angles='xy', scale_units='xy', scale=1, color='teal')
plt.plot(df['Day_Num'], df['New_Cases'], color='gray', alpha=0.5, label='New Cases')
plt.plot(df['Day_Num'], trend, color='red', linestyle='--', label='Linear Trend')
plt.title('Quiver Plot - Change in COVID-19 Cases Over Time')
plt.xlabel('Days Since Start')
plt.ylabel('New Cases')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
5. 极坐标图
我尝试用极坐标图来表示 COVID-19 数据,但说实话,不太合适。我意识到,极坐标图更适合周期性数据,例如风向或温度模式,而不是像每日 COVID-19 病例这样的线性时间序列数据,因为这些数据不遵循自然的循环或周期。
不过,我们还是试了一下,以下是我们观察到的结果。
这里所有的视觉效果都是用Matplotlib实现的,它非常适合绘制静态图。从调整颜色到图形大小,一切都是手动调整的。
其中一个挑战是在球形比例尺上绘制数值,这并不简单。但在NumPy的帮助下,我们解决了这个问题。
2 * np.pi
我们将天数转换为弧度,这至关重要,因为Matplotlib 的极坐标绘图需要以弧度为单位的角度- 而不是度数或日期。
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# 加载数据
df = pd.read_csv('covid_cases.csv')
df['Date'] = pd.to_datetime(df['Date'])
# 为每一天分配一个角度(循环 360°)
df['Day_Num'] = (df['Date'] - df['Date'].min()).dt.days
theta = 2 * np.pi * df['Day_Num'] / df['Day_Num'].max() # 圆周角度
r = df['New_Cases']
# 绘图
plt.figure(figsize=(8, 8))
ax = plt.subplot(111, polar=True)
ax.plot(theta, r, color='darkblue', linewidth=2)
ax.fill(theta, r, alpha=0.3, color='skyblue')
ax.set_title('Polar Plot of COVID-19 New Cases Over Time', va='bottom')
ax.set_theta_zero_location('N') # 从顶部开始
ax.set_theta_direction(-1) # 顺时针
plt.tight_layout()
plt.show()
我们必须将线性数据强制转换为循环形式,这并不自然地适合 COVID-19 趋势——但我们仍然尝试了一下。
以下是我们的映射方式:
0 度代表2020 年 12 月 1 日 360度代表2020年12月的最后一天
向外球形移动的虚线代表COVID -19 病例数。
当线靠近边缘时,当天的病例数较高。 当线靠近外圈时,疫情越严重。
6.波特图
用波特图来表示 COVID-19 数据是一个疯狂而又创新的想法,因为它最初并非为这种可视化而设计的。波特图最常用于电气工程——用于分析系统中的频率、增益和相位。
但我发现了一个非常有趣的事实:我们能够突显传统 COVID-19 图表经常忽略的模式。通过转换到频域,幅度与频率视图有助于揭示数据中重复出现的趋势。
在此背景下:
Y 轴表示幅度(模式的强度或密集程度) X 轴显示频率(模式出现的频率)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 步骤 1:从本地计算机加载 CSV 文件
# 确保 'covid_cases.csv' 位于同一目录中或提供完整路径
df = pd.read_csv('covid_cases.csv')
df['Date'] = pd.to_datetime(df['Date'])
# 步骤 2:阶段分类
defclassify_phase(cases):
if cases < 2000:
return'Low'
elif cases < 3500:
return'Moderate'
else:
return'High'
df['Phase'] = df['New_Cases'].apply(classify_phase)
# 步骤 3:绘制相图
colors = {'Low': 'green', 'Moderate': 'orange', 'High': 'red'}
plt.figure(figsize=(12, 5))
for phase in df['Phase'].unique():
subset = df[df['Phase'] == phase]
plt.plot(subset['Date'], subset['New_Cases'], label=phase, color=colors[phase])
plt.title('Phase Diagram - COVID-19 Case Levels Over Time')
plt.xlabel('Date')
plt.ylabel('New Cases')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# 步骤 4:Bode 风格频率分析
cases = df['New_Cases'].values
fft_vals = np.fft.fft(cases)
freqs = np.fft.fftfreq(len(cases), d=1)
plt.figure(figsize=(12, 5))
plt.plot(freqs[1:len(cases)//2], 20 * np.log10(np.abs(fft_vals[1:len(cases)//2])), color='darkblue')
plt.title('Bode-style Plot - Frequency Components in COVID-19 Case Trends')
plt.xlabel('Frequency')
plt.ylabel('Magnitude (dB)')
plt.grid(True)
plt.tight_layout()
plt.show()
为了使问题更加简单明了,下面是我们使用博德图观察到的结果的快速细分:
在 0.1 频率(10 天周期):案例的震级为85 dB,相当强。例如: 12月1日:1,120例 - 12月10日:2,100例 - 12月20日:3,120例。这显示出每10天重复一次的清晰而急剧的上升趋势。波特图以0.1的频率捕捉到了这种模式,这代表每10天循环一次。 在 0.2 频率(5 天周期):振幅为 75 分贝,比 10 天周期稍弱。采样日: 12 月 5 日 → 12 月 10 日 → 12 月 15 日 这种模式确实存在,但不太明显。 在频率为0.4(2-3天周期)时: 未观察到任何有意义的模式。 图表显示其震级较低,表明在如此短的时间间隔内没有真正的重复。
7. 相图
相图以展示不同阶段的数据以及在不同条件下发生的变化而闻名。它专为科学数据可视化而设计,常用于温度和压力分析等场景。
但当我将它应用于COVID-19 数据时,结果却出奇地有效。
我们能够使用Pandas以及Matplotlib等静态图表工具来实现这一点。Matplotlib 的绘图功能(在其官方网站上有详尽的文档)帮助我们清晰地可视化数据中的各个阶段。脚本的演示如下所示。
您可能注意到脚本中一个重要的部分是使用了if condition。这样做的原因很简单:定义哪些病例算作严重病例。例如,我们设置了 3,500 例这样的阈值。这种分类不仅增加了逻辑性,也增强了阶段图的表示。
3,500 例这个数字也可能受到医院床位可用情况的影响。当病例数量超过医院容量时,许多患者可能仍无法进入医院系统,这正是我们选择这个门槛的原因。
import pandas as pd
import matplotlib.pyplot as plt
# 步骤 1:从本地文件系统加载 CSV 文件
# 确保 'covid_cases.csv' 位于同一目录中或提供完整路径
df = pd.read_csv('covid_cases.csv')
df['Date'] = pd.to_datetime(df['Date'])
# 步骤 2:根据新病例数对阶段进行分类
defclassify_phase(cases):
if cases < 2000:
return'Low'
elif cases < 3500:
return'Moderate'
else:
return'High'
df['Phase'] = df['New_Cases'].apply(classify_phase)
# 步骤 3:绘制随时间变化的阶段
colors = {'Low': 'green', 'Moderate': 'orange', 'High': 'red'}
plt.figure(figsize=(12, 6))
for phase in df['Phase'].unique():
subset = df[df['Phase'] == phase]
plt.plot(subset['Date'], subset['New_Cases'], label=phase, color=colors[phase])
plt.title('Phase Diagram - COVID-19 Case Levels Over Time')
plt.xlabel('Date')
plt.ylabel('New Cases')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()r on Medium, and a huge Python enthusiast. Thanks for sticking with me till the end!
我将其应用于COVID-19 病例计数,将时间线分为三个不同的阶段: 低(少于2000例) 中(2000–3499例) 高(3500+例)
🏴☠️宝藏级🏴☠️ 原创公众号『数据STUDIO』内容超级硬核。公众号以Python为核心语言,垂直于数据科学领域,包括可戳👉Python|MySQL|数据分析|数据可视化|机器学习与数据挖掘|爬虫等,从入门到进阶!
长按👇关注- 数据STUDIO -设为星标,干货速递