如何使用Matplotlib创建时间序列螺旋图
参考:Creating a Temporal Range Time-Series Spiral Plot
时间序列螺旋图是一种独特而强大的数据可视化方式,它能够在一个紧凑的螺旋形状中展示长时间跨度的数据变化。这种图表特别适合展示周期性数据,如每日、每周或每月的模式。在本文中,我们将深入探讨如何使用Python的Matplotlib库来创建这种引人注目的可视化图表。
1. 时间序列螺旋图的基本概念
时间序列螺旋图的核心思想是将时间轴沿着螺旋线展开,数据点则根据其时间戳和数值分布在螺旋上。这种布局方式允许我们在一个紧凑的空间内展示大量的时间序列数据,同时还能直观地展示周期性模式。
让我们从一个简单的例子开始:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
import datetime
# 生成示例数据
days = 365
start_date = datetime.datetime(2023, 1, 1)
dates = [start_date + datetime.timedelta(days=i) for i in range(days)]
values = np.sin(np.linspace(0, 4*np.pi, days)) + np.random.randn(days)*0.1
# 计算螺旋坐标
t = np.linspace(0, 2*np.pi, days)
r = np.linspace(0, 1, days)
x = r * np.cos(t)
y = r * np.sin(t)
# 创建颜色映射
norm = Normalize(vmin=min(values), vmax=max(values))
colors = plt.cm.viridis(norm(values))
# 创建线段集合
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, colors=colors, linewidth=2)
# 创建图表
fig, ax = plt.subplots(figsize=(10, 10))
ax.add_collection(lc)
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(-1.1, 1.1)
ax.axis('off')
# 添加颜色条
sm = plt.cm.ScalarMappable(cmap=plt.cm.viridis, norm=norm)
sm.set_array([])
cbar = plt.colorbar(sm)
cbar.set_label('Value')
plt.title('Time Series Spiral Plot - how2matplotlib.com')
plt.show()
在这个例子中,我们生成了一年的正弦波数据,并将其绘制成一个简单的螺旋图。颜色表示数值的大小,从内到外代表时间的流逝。
2. 数据准备
创建时间序列螺旋图的第一步是准备合适的数据。通常,我们需要两个主要的数据组件:时间戳和对应的数值。
2.1 生成时间戳
对于时间序列数据,我们通常使用Python的datetime
模块来处理时间。以下是一个生成日期序列的例子:
import datetime
start_date = datetime.datetime(2023, 1, 1)
days = 365
dates = [start_date + datetime.timedelta(days=i) for i in range(days)]
print(f"Generated dates for how2matplotlib.com: {dates[:5]}...")
Output:
这段代码生成了从2023年1月1日开始的365天的日期序列。
2.2 生成或导入数值数据
接下来,我们需要为每个时间戳准备对应的数值。这可以是实际的观测数据,也可以是为了演示而生成的模拟数据。以下是一个生成模拟数据的例子:
import numpy as np
# 生成带有一些随机噪声的正弦波数据
values = np.sin(np.linspace(0, 4*np.pi, days)) + np.random.randn(days)*0.1
print(f"Generated values for how2matplotlib.com: {values[:5]}...")
这段代码生成了一个带有随机噪声的正弦波数据序列,长度与日期序列相匹配。
3. 计算螺旋坐标
要创建螺旋图,我们需要将时间和数值映射到二维平面上的螺旋形状。这涉及到将线性时间转换为极坐标,然后再转换为笛卡尔坐标。
import numpy as np
# 生成螺旋坐标
t = np.linspace(0, 2*np.pi, days) # 角度
r = np.linspace(0, 1, days) # 半径
x = r * np.cos(t)
y = r * np.sin(t)
print(f"Spiral coordinates for how2matplotlib.com: (x[0], y[0]) = ({x[0]:.2f}, {y[0]:.2f})")
在这个例子中,t
表示角度,随时间均匀增加;r
表示半径,从中心向外均匀增加。x
和y
是最终的笛卡尔坐标。
4. 创建基本的螺旋图
有了坐标后,我们可以开始创建基本的螺旋图了。我们将使用Matplotlib的LineCollection
来高效地绘制螺旋线。
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
# 创建颜色映射
norm = Normalize(vmin=min(values), vmax=max(values))
colors = plt.cm.viridis(norm(values))
# 创建线段集合
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, colors=colors, linewidth=2)
# 创建图表
fig, ax = plt.subplots(figsize=(10, 10))
ax.add_collection(lc)
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(-1.1, 1.1)
ax.axis('off')
plt.title('Basic Time Series Spiral Plot - how2matplotlib.com')
plt.show()
这段代码创建了一个基本的时间序列螺旋图。颜色表示数值的大小,从内到外代表时间的流逝。
5. 添加颜色条
为了让读者更容易理解颜色所代表的数值,我们可以添加一个颜色条:
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
# ... (前面的代码保持不变)
# 添加颜色条
sm = plt.cm.ScalarMappable(cmap=plt.cm.viridis, norm=norm)
sm.set_array([])
cbar = plt.colorbar(sm)
cbar.set_label('Value')
plt.title('Time Series Spiral Plot with Colorbar - how2matplotlib.com')
plt.show()
这个颜色条显示了颜色与数值之间的对应关系,使图表更加易于理解。
6. 自定义颜色映射
Matplotlib提供了多种内置的颜色映射,我们可以根据数据的特性选择最合适的一种。以下是使用不同颜色映射的例子:
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
# ... (前面的代码保持不变)
# 使用不同的颜色映射
color_maps = ['viridis', 'plasma', 'inferno', 'magma']
fig, axs = plt.subplots(2, 2, figsize=(15, 15))
axs = axs.ravel()
for i, cmap in enumerate(color_maps):
colors = plt.get_cmap(cmap)(norm(values))
lc = LineCollection(segments, colors=colors, linewidth=2)
axs[i].add_collection(lc)
axs[i].set_xlim(-1.1, 1.1)
axs[i].set_ylim(-1.1, 1.1)
axs[i].axis('off')
axs[i].set_title(f'{cmap.capitalize()} - how2matplotlib.com')
plt.tight_layout()
plt.show()
这个例子展示了四种不同的颜色映射效果,让你可以选择最适合你的数据的一种。
7. 添加时间标记
为了让读者更容易理解螺旋图中的时间流逝,我们可以在螺旋的某些位置添加时间标记:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
import datetime
# ... (前面的代码保持不变)
fig, ax = plt.subplots(figsize=(12, 12))
ax.add_collection(lc)
# 添加月份标记
for i in range(0, days, 30):
angle = 2 * np.pi * i / days
r = i / days
x = 1.1 * r * np.cos(angle)
y = 1.1 * r * np.sin(angle)
month = (start_date + datetime.timedelta(days=i)).strftime('%b')
ax.text(x, y, month, ha='center', va='center')
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.axis('off')
plt.title('Time Series Spiral Plot with Month Labels - how2matplotlib.com')
plt.show()
这个例子在螺旋的外围添加了月份标签,使得时间的流逝更加直观。
8. 处理多年数据
如果我们的数据跨越多年,我们可能希望每一圈代表一年。以下是一个处理多年数据的例子:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
import datetime
# 生成3年的数据
years = 3
days = 365 * years
start_date = datetime.datetime(2021, 1, 1)
dates = [start_date + datetime.timedelta(days=i) for i in range(days)]
values = np.sin(np.linspace(0, 2*np.pi*years, days)) + np.random.randn(days)*0.1
# 计算螺旋坐标
t = np.linspace(0, 2*np.pi*years, days)
r = np.linspace(0, 1, days)
x = r * np.cos(t)
y = r * np.sin(t)
# 创建颜色映射
norm = Normalize(vmin=min(values), vmax=max(values))
colors = plt.cm.viridis(norm(values))
# 创建线段集合
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, colors=colors, linewidth=2)
# 创建图表
fig, ax = plt.subplots(figsize=(12, 12))
ax.add_collection(lc)
# 添加年份标记
for i in range(years):
angle = 2 * np.pi * i
x = 1.1 * np.cos(angle)
y = 1.1 * np.sin(angle)
year = start_date.year + i
ax.text(x, y, str(year), ha='center', va='center', fontweight='bold')
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.axis('off')
plt.title('Multi-Year Time Series Spiral Plot - how2matplotlib.com')
plt.show()
Output:
这个例子创建了一个跨越3年的螺旋图,每一圈代表一年,并在适当的位置标注了年份。
9. 添加交互性
为了增加图表的交互性,我们可以使用Matplotlib的事件处理功能来添加鼠标悬停效果,显示具体的日期和数值:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
import datetime
# ... (前面的代码保持不变)
fig, ax = plt.subplots(figsize=(12, 12))
ax.add_collection(lc)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.axis('off')
# 添加文本注释
annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(ind):
pos = lc.get_segments()[ind["ind"][0]]
annot.xy = pos[0]
text = f"{dates[ind['ind'][0]].strftime('%Y-%m-%d')}\nValue: {values[ind['ind'][0]]:.2f}"
annot.set_text(text)
annot.get_bbox_patch().set_alpha(0.4)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = lc.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.title('Interactive Time Series Spiral Plot - how2matplotlib.com')
plt.show()
```这个例子添加了鼠标悬停功能,当鼠标移动到螺旋线上的某个点时,会显示该点对应的具体日期和数值。
## 10. 自定义螺旋形状
我们可以通过调整螺旋的参数来改变其形状,例如创建一个阿基米德螺旋:
```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
import datetime
# 生成数据
days = 365
start_date = datetime.datetime(2023, 1, 1)
dates = [start_date + datetime.timedelta(days=i) for i in range(days)]
values = np.sin(np.linspace(0, 4*np.pi, days)) + np.random.randn(days)*0.1
# 计算阿基米德螺旋坐标
t = np.linspace(0, 10*np.pi, days)
a = 0.1 # 控制螺旋的紧密程度
r = a * t
x = r * np.cos(t)
y = r * np.sin(t)
# 创建颜色映射
norm = Normalize(vmin=min(values), vmax=max(values))
colors = plt.cm.viridis(norm(values))
# 创建线段集合
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, colors=colors, linewidth=2)
# 创建图表
fig, ax = plt.subplots(figsize=(12, 12))
ax.add_collection(lc)
ax.set_xlim(min(x)-0.1, max(x)+0.1)
ax.set_ylim(min(y)-0.1, max(y)+0.1)
ax.axis('off')
plt.title('Archimedes Spiral Time Series Plot - how2matplotlib.com')
plt.show()
Output:
这个例子创建了一个阿基米德螺旋形状的时间序列图,螺旋的间距随时间均匀增加。
11. 添加径向网格
为了更好地展示时间的流逝,我们可以添加径向网格线:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
import datetime
# ... (前面的代码保持不变)
fig, ax = plt.subplots(figsize=(12, 12))
ax.add_collection(lc)
# 添加径向网格
for i in range(1, 13):
angle = 2 * np.pi * i / 12
ax.plot([0, np.cos(angle)], [0, np.sin(angle)], 'k-', alpha=0.1)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.axis('off')
plt.title('Time Series Spiral Plot with Radial Grid - how2matplotlib.com')
plt.show()
这个例子在螺旋图上添加了12条径向线,代表一年中的12个月,使时间的流逝更加直观。
12. 处理缺失数据
在实际应用中,我们可能会遇到数据缺失的情况。以下是一个处理缺失数据的例子:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
import datetime
# 生成带有缺失值的数据
days = 365
start_date = datetime.datetime(2023, 1, 1)
dates = [start_date + datetime.timedelta(days=i) for i in range(days)]
values = np.sin(np.linspace(0, 4*np.pi, days)) + np.random.randn(days)*0.1
# 随机将10%的数据设为NaN
mask = np.random.choice([True, False], size=days, p=[0.1, 0.9])
values[mask] = np.nan
# 计算螺旋坐标
t = np.linspace(0, 2*np.pi, days)
r = np.linspace(0, 1, days)
x = r * np.cos(t)
y = r * np.sin(t)
# 创建颜色映射,将NaN值设为灰色
norm = Normalize(vmin=np.nanmin(values), vmax=np.nanmax(values))
colors = np.array([plt.cm.viridis(norm(v)) if not np.isnan(v) else [0.7, 0.7, 0.7, 1] for v in values])
# 创建线段集合
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, colors=colors, linewidth=2)
# 创建图表
fig, ax = plt.subplots(figsize=(12, 12))
ax.add_collection(lc)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.axis('off')
plt.title('Time Series Spiral Plot with Missing Data - how2matplotlib.com')
plt.show()
Output:
在这个例子中,我们将缺失的数据点用灰色表示,使得数据的完整性一目了然。
13. 多变量时间序列螺旋图
有时我们可能需要在同一个图表中比较多个变量的时间序列。以下是一个创建多变量时间序列螺旋图的例子:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
import datetime
# 生成多变量数据
days = 365
start_date = datetime.datetime(2023, 1, 1)
dates = [start_date + datetime.timedelta(days=i) for i in range(days)]
values1 = np.sin(np.linspace(0, 4*np.pi, days)) + np.random.randn(days)*0.1
values2 = np.cos(np.linspace(0, 4*np.pi, days)) + np.random.randn(days)*0.1
# 计算螺旋坐标
t = np.linspace(0, 2*np.pi, days)
r1 = np.linspace(0, 0.8, days)
r2 = np.linspace(0.2, 1, days)
x1, y1 = r1 * np.cos(t), r1 * np.sin(t)
x2, y2 = r2 * np.cos(t), r2 * np.sin(t)
# 创建颜色映射
norm1 = Normalize(vmin=min(values1), vmax=max(values1))
norm2 = Normalize(vmin=min(values2), vmax=max(values2))
colors1 = plt.cm.viridis(norm1(values1))
colors2 = plt.cm.plasma(norm2(values2))
# 创建线段集合
points1 = np.array([x1, y1]).T.reshape(-1, 1, 2)
points2 = np.array([x2, y2]).T.reshape(-1, 1, 2)
segments1 = np.concatenate([points1[:-1], points1[1:]], axis=1)
segments2 = np.concatenate([points2[:-1], points2[1:]], axis=1)
lc1 = LineCollection(segments1, colors=colors1, linewidth=2)
lc2 = LineCollection(segments2, colors=colors2, linewidth=2)
# 创建图表
fig, ax = plt.subplots(figsize=(12, 12))
ax.add_collection(lc1)
ax.add_collection(lc2)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.axis('off')
# 添加图例
sm1 = plt.cm.ScalarMappable(cmap=plt.cm.viridis, norm=norm1)
sm2 = plt.cm.ScalarMappable(cmap=plt.cm.plasma, norm=norm2)
cbar1 = plt.colorbar(sm1, ax=ax, orientation='vertical', fraction=0.046, pad=0.04)
cbar2 = plt.colorbar(sm2, ax=ax, orientation='vertical', fraction=0.046, pad=0.09)
cbar1.set_label('Variable 1')
cbar2.set_label('Variable 2')
plt.title('Multi-variable Time Series Spiral Plot - how2matplotlib.com')
plt.show()
Output:
这个例子展示了如何在同一个螺旋图中比较两个不同的时间序列变量。
14. 动态时间序列螺旋图
为了展示数据随时间的变化,我们可以创建一个动态的时间序列螺旋图:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.collections import LineCollection
from matplotlib.colors import Normalize
import datetime
# 生成数据
days = 365
start_date = datetime.datetime(2023, 1, 1)
dates = [start_date + datetime.timedelta(days=i) for i in range(days)]
values = np.sin(np.linspace(0, 4*np.pi, days)) + np.random.randn(days)*0.1
# 计算螺旋坐标
t = np.linspace(0, 2*np.pi, days)
r = np.linspace(0, 1, days)
x = r * np.cos(t)
y = r * np.sin(t)
# 创建图表
fig, ax = plt.subplots(figsize=(10, 10))
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.axis('off')
line, = ax.plot([], [], lw=2)
# 初始化函数
def init():
line.set_data([], [])
return line,
# 动画更新函数
def update(frame):
line.set_data(x[:frame], y[:frame])
line.set_color(plt.cm.viridis(norm(values[:frame])))
ax.set_title(f'Time Series Spiral Plot - {dates[frame].strftime("%Y-%m-%d")} - how2matplotlib.com')
return line,
# 创建颜色映射
norm = Normalize(vmin=min(values), vmax=max(values))
# 创建动画
anim = FuncAnimation(fig, update, frames=days, init_func=init, blit=True, interval=50)
plt.show()
这个例子创建了一个动态的时间序列螺旋图,展示了数据如何随时间变化而逐渐形成螺旋形状。
15. 结论
时间序列螺旋图是一种强大的数据可视化工具,特别适合展示周期性数据和长时间序列。通过使用Matplotlib,我们可以创建各种自定义的螺旋图,包括静态的、交互式的和动态的版本。这种可视化方法可以帮助我们发现数据中的模式、趋势和异常,是数据分析和展示的有力工具。
在本文中,我们探讨了创建时间序列螺旋图的多个方面,包括基本绘图、颜色映射、处理多年数据、添加交互性、自定义螺旋形状、处理缺失数据以及创建多变量和动态螺旋图。这些技术可以根据具体的数据和分析需求进行组合和调整,以创建最适合你的数据的可视化效果。
时间序列螺旋图的应用范围非常广泛,从气象数据分析到股票市场趋势预测,再到生物节律研究,都可以找到它的身影。通过掌握这种可视化技术,你将能够更有效地探索和展示时间序列数据,为你的数据分析工作增添新的维度。
记住,好的数据可视化不仅仅是美观,更重要的是能够有效地传达信息。在创建时间序列螺旋图时,始终要考虑你的目标受众和你想要传达的核心信息。通过适当的颜色选择、标签添加和交互设计,你可以确保你的可视化既引人注目又富有洞察力。
最后,随着数据可视化技术的不断发展,我们期待看到更多创新的时间序列数据展示方式。时间序列螺旋图是一个很好的起点,但不要止步于此。继续探索、实验和创新,你可能会发现更多令人兴奋的数据可视化方法。