一、基本图形分类
1. 基础图形概览
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as patches
# 二维图形主要类型
plot_types = {
'折线图': 'plot', # 趋势、变化
'散点图': 'scatter', # 分布、相关性
'柱状图': 'bar/barh', # 比较、分类
'直方图': 'hist', # 分布、频率
'饼图': 'pie', # 比例、构成
'箱线图': 'boxplot', # 统计、异常值
'面积图': 'fill_between', # 累积、堆叠
'等高线': 'contour', # 等值线、地形
'热力图': 'imshow', # 矩阵、相关性
'矢量图': 'quiver', # 方向、场
'极坐标': 'polar', # 角度、径向
'对数图': 'loglog/semilogx/semilogy', # 指数关系
}二、折线图家族
1. 基础折线图
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 1. 单线折线图
ax = axes[0, 0]
x = np.linspace(0, 10, 100)
y = np.sin(x)
ax.plot(x, y, 'b-', linewidth=2, label='sin(x)')
ax.set_title('单线折线图', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
# 2. 多线折线图
ax = axes[0, 1]
x = np.linspace(0, 2*np.pi, 100)
for i in range(5):
y = np.sin(x + i*0.5)
ax.plot(x, y, linewidth=2, label=f'相位 {i}')
ax.set_title('多线折线图', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
# 3. 带标记折线图
ax = axes[0, 2]
x = np.arange(10)
y = np.random.randn(10).cumsum()
ax.plot(x, y, 'o-', markersize=8, linewidth=2,
markerfacecolor='white', markeredgecolor='blue',
markeredgewidth=2)
ax.set_title('带标记折线图', fontsize=12)
ax.grid(True, alpha=0.3)
# 4. 阶梯图
ax = axes[1, 0]
x = np.arange(10)
y = np.random.randint(1, 10, 10)
ax.step(x, y, where='mid', linewidth=2)
ax.set_title('阶梯图 (where="mid")', fontsize=12)
ax.grid(True, alpha=0.3)
# 5. 填充线图
ax = axes[1, 1]
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
ax.plot(x, y1, 'b-', label='sin(x)')
ax.plot(x, y2, 'r-', label='cos(x)')
ax.fill_between(x, y1, y2, where=(y1 > y2),
color='green', alpha=0.3)
ax.set_title('填充线图', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
# 6. 误差线折线图
ax = axes[1, 2]
x = np.arange(10)
y = np.random.randn(10).cumsum()
y_err = np.random.rand(10) * 2
ax.errorbar(x, y, yerr=y_err, fmt='o-',
capsize=5, capthick=2,
elinewidth=2, ecolor='red',
label='带误差线')
ax.set_title('误差线折线图', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
plt.suptitle('折线图家族', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()2. 高级折线图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 1. 双Y轴折线图
ax1 = axes[0, 0]
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.exp(x/5)
color1 = 'tab:blue'
color2 = 'tab:red'
ax1.plot(x, y1, color=color1, linewidth=2)
ax1.set_xlabel('X轴')
ax1.set_ylabel('sin(x)', color=color1)
ax1.tick_params(axis='y', labelcolor=color1)
ax2 = ax1.twinx()
ax2.plot(x, y2, color=color2, linestyle='--', linewidth=2)
ax2.set_ylabel('exp(x/5)', color=color2)
ax2.tick_params(axis='y', labelcolor=color2)
ax1.set_title('双Y轴折线图', fontsize=12)
ax1.grid(True, alpha=0.3)
# 2. 对数坐标折线图
ax = axes[0, 1]
x = np.logspace(0, 3, 100) # 10^0 到 10^3
y = x**2
ax.loglog(x, y, linewidth=2) # 双对数坐标
ax.set_xlabel('X (log scale)')
ax.set_ylabel('Y (log scale)')
ax.set_title('双对数坐标折线图', fontsize=12)
ax.grid(True, alpha=0.3, which='both') # both=主副网格
# 3. 半对数坐标折线图
ax = axes[1, 0]
x = np.linspace(0, 10, 100)
y = np.exp(x/3)
ax.semilogy(x, y, linewidth=2) # Y轴对数
ax.set_xlabel('X')
ax.set_ylabel('Y (log scale)')
ax.set_title('半对数坐标折线图 (Y轴对数)', fontsize=12)
ax.grid(True, alpha=0.3, which='both')
# 4. 带置信区间折线图
ax = axes[1, 1]
np.random.seed(42)
x = np.arange(20)
y_mean = np.sin(x * 0.3) + 2
y_std = 0.3 + 0.1 * np.sin(x * 0.5)
# 生成多条曲线模拟置信区间
n_samples = 50
y_samples = np.array([y_mean + np.random.randn(len(x)) * y_std
for _ in range(n_samples)])
y_upper = np.percentile(y_samples, 95, axis=0)
y_lower = np.percentile(y_samples, 5, axis=0)
ax.plot(x, y_mean, 'b-', linewidth=3, label='均值')
ax.fill_between(x, y_lower, y_upper,
color='blue', alpha=0.2, label='90%置信区间')
ax.set_title('带置信区间折线图', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
plt.suptitle('高级折线图', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()三、散点图家族
1. 基础散点图
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
np.random.seed(42)
n_points = 100
# 1. 基本散点图
ax = axes[0, 0]
x = np.random.randn(n_points)
y = np.random.randn(n_points)
ax.scatter(x, y, s=50, alpha=0.7)
ax.set_title('基本散点图', fontsize=12)
ax.grid(True, alpha=0.3)
# 2. 颜色映射散点图
ax = axes[0, 1]
x = np.random.randn(n_points)
y = np.random.randn(n_points)
z = np.sqrt(x**2 + y**2) # 到原点的距离
scatter = ax.scatter(x, y, c=z, s=50,
cmap='viridis', alpha=0.7)
ax.set_title('颜色映射散点图', fontsize=12)
plt.colorbar(scatter, ax=ax)
ax.grid(True, alpha=0.3)
# 3. 大小映射散点图(气泡图)
ax = axes[0, 2]
x = np.random.randn(n_points)
y = np.random.randn(n_points)
sizes = np.random.rand(n_points) * 200 # 点的大小
scatter = ax.scatter(x, y, s=sizes, alpha=0.6,
edgecolors='black', linewidth=0.5)
ax.set_title('气泡图', fontsize=12)
ax.grid(True, alpha=0.3)
# 4. 标记样式散点图
ax = axes[1, 0]
markers = ['o', 's', '^', 'D', 'v', '<', '>', 'p', '*', 'X']
categories = np.random.randint(0, len(markers), n_points)
for i, marker in enumerate(markers):
mask = categories == i
ax.scatter(x[mask], y[mask], marker=marker,
s=50, alpha=0.7, label=f'类别{i+1}')
ax.set_title('多标记样式散点图', fontsize=12)
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax.grid(True, alpha=0.3)
# 5. 边缘直方图散点图
from mpl_toolkits.axes_grid1 import make_axes_locatable
ax = axes[1, 1]
x = np.random.randn(n_points)
y = np.random.randn(n_points)
ax.scatter(x, y, s=50, alpha=0.7)
# 添加边缘直方图
divider = make_axes_locatable(ax)
ax_histx = divider.append_axes("top", size="20%", pad=0.1, sharex=ax)
ax_histy = divider.append_axes("right", size="20%", pad=0.1, sharey=ax)
ax_histx.hist(x, bins=30, alpha=0.7, density=True)
ax_histy.hist(y, bins=30, alpha=0.7, density=True,
orientation='horizontal')
ax_histx.axis('off')
ax_histy.axis('off')
ax.set_title('带边缘直方图散点图', fontsize=12)
# 6. 密度散点图
ax = axes[1, 2]
from scipy.stats import gaussian_kde
# 生成聚集数据
np.random.seed(42)
x = np.concatenate([np.random.randn(100)*0.5 + 2,
np.random.randn(100)*0.3 - 1])
y = np.concatenate([np.random.randn(100)*0.5 + 1,
np.random.randn(100)*0.3 - 2])
# 计算点密度
xy = np.vstack([x, y])
z = gaussian_kde(xy)(xy)
scatter = ax.scatter(x, y, c=z, s=50, cmap='viridis',
alpha=0.7, edgecolor='white', linewidth=0.5)
ax.set_title('密度散点图', fontsize=12)
plt.colorbar(scatter, ax=ax)
ax.grid(True, alpha=0.3)
plt.suptitle('散点图家族', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()2. 高级散点图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 1. 带回归线散点图
ax = axes[0, 0]
np.random.seed(42)
x = np.random.randn(100)
y = 2*x + 1 + np.random.randn(100)*0.5
# 计算回归线
coeffs = np.polyfit(x, y, 1)
poly = np.poly1d(coeffs)
x_fit = np.linspace(x.min(), x.max(), 100)
y_fit = poly(x_fit)
ax.scatter(x, y, s=50, alpha=0.7, label='数据点')
ax.plot(x_fit, y_fit, 'r-', linewidth=2,
label=f'y = {coeffs[0]:.2f}x + {coeffs[1]:.2f}')
# 计算R²
residuals = y - poly(x)
ss_res = np.sum(residuals**2)
ss_tot = np.sum((y - np.mean(y))**2)
r_squared = 1 - (ss_res / ss_tot)
ax.text(0.05, 0.95, f'R² = {r_squared:.3f}',
transform=ax.transAxes, fontsize=12,
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
ax.set_title('带回归线散点图', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
# 2. 时间序列散点图
ax = axes[0, 1]
import pandas as pd
dates = pd.date_range('2023-01-01', periods=50, freq='D')
values = np.random.randn(50).cumsum()
volumes = np.random.randint(100, 1000, 50)
scatter = ax.scatter(dates, values, s=volumes/10,
c=volumes, cmap='coolwarm',
alpha=0.7, edgecolor='black', linewidth=0.5)
ax.set_title('时间序列气泡图', fontsize=12)
ax.set_xlabel('日期')
ax.set_ylabel('数值')
plt.colorbar(scatter, ax=ax, label='交易量')
ax.grid(True, alpha=0.3)
plt.xticks(rotation=45)
# 3. 分类散点图
ax = axes[1, 0]
from sklearn.datasets import make_classification
# 生成分类数据
X, y = make_classification(n_samples=200, n_features=2,
n_redundant=0, n_clusters_per_class=1,
random_state=42)
# 使用不同形状标记不同类别
markers = ['o', 's']
for i in range(2):
mask = y == i
ax.scatter(X[mask, 0], X[mask, 1],
marker=markers[i], s=50, alpha=0.7,
label=f'类别 {i}')
# 添加决策边界(模拟)
from matplotlib.colors import ListedColormap
xx, yy = np.meshgrid(np.linspace(-3, 3, 200),
np.linspace(-3, 3, 200))
Z = xx**2 + yy**2 - 2 # 简单决策边界
ax.contour(xx, yy, Z, levels=[0], colors='red',
linewidths=2, linestyles='--')
ax.set_title('分类散点图(带决策边界)', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
# 4. 相关性矩阵散点图
ax = axes[1, 1]
import seaborn as sns
# 创建相关数据集
np.random.seed(42)
n_vars = 4
n_points = 100
data = np.random.randn(n_points, n_vars)
# 添加相关性
data[:, 1] = 0.7*data[:, 0] + 0.3*np.random.randn(n_points)
data[:, 2] = -0.5*data[:, 0] + 0.5*np.random.randn(n_points)
data[:, 3] = 0.3*data[:, 1] + 0.7*np.random.randn(n_points)
# 创建散点图矩阵
fig2, axs = plt.subplots(n_vars, n_vars, figsize=(10, 10))
fig2.delaxes(axs[0, 0]) # 删除左上角
for i in range(n_vars):
for j in range(n_vars):
if i != j:
ax = axs[i, j]
ax.scatter(data[:, j], data[:, i],
s=30, alpha=0.6, edgecolor='white', linewidth=0.5)
# 计算并显示相关系数
corr = np.corrcoef(data[:, j], data[:, i])[0, 1]
ax.text(0.05, 0.95, f'r={corr:.2f}',
transform=ax.transAxes, fontsize=9,
bbox=dict(boxstyle='round', facecolor='white', alpha=0.7))
# 设置刻度
ax.set_xticks([])
ax.set_yticks([])
# 只在边缘显示标签
if j == 0:
ax.set_ylabel(f'Var {i+1}', fontsize=10)
if i == n_vars-1:
ax.set_xlabel(f'Var {j+1}', fontsize=10)
else:
# 对角线:显示变量名
ax = axs[i, j]
ax.text(0.5, 0.5, f'Var {i+1}',
ha='center', va='center', fontsize=12)
ax.axis('off')
plt.suptitle('相关性矩阵散点图', fontsize=16, y=0.92)
plt.tight_layout()
plt.show()四、柱状图家族
1. 基础柱状图
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
np.random.seed(42)
categories = ['A', 'B', 'C', 'D', 'E', 'F']
values = np.random.randint(10, 100, len(categories))
# 1. 垂直柱状图
ax = axes[0, 0]
bars = ax.bar(categories, values,
color='skyblue', edgecolor='navy', linewidth=2)
# 添加数值标签
for bar in bars:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height,
f'{height}', ha='center', va='bottom')
ax.set_title('垂直柱状图', fontsize=12)
ax.set_ylabel('数值')
ax.grid(True, alpha=0.3, axis='y')
# 2. 水平柱状图
ax = axes[0, 1]
bars = ax.barh(categories, values,
color='lightcoral', edgecolor='darkred', linewidth=2)
# 添加数值标签
for bar in bars:
width = bar.get_width()
ax.text(width, bar.get_y() + bar.get_height()/2.,
f'{width}', ha='left', va='center')
ax.set_title('水平柱状图', fontsize=12)
ax.set_xlabel('数值')
ax.grid(True, alpha=0.3, axis='x')
# 3. 分组柱状图
ax = axes[0, 2]
n_groups = 4
n_bars = 3
x = np.arange(n_groups)
width = 0.25
for i in range(n_bars):
bar_values = np.random.randint(10, 50, n_groups)
bars = ax.bar(x + i*width, bar_values, width,
label=f'组{i+1}', alpha=0.8)
ax.set_title('分组柱状图', fontsize=12)
ax.set_xticks(x + width)
ax.set_xticklabels([f'类别{i+1}' for i in range(n_groups)])
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
# 4. 堆叠柱状图
ax = axes[1, 0]
n_categories = 5
n_stacks = 3
bottom = np.zeros(n_categories)
colors = ['#FF9999', '#66B2FF', '#99FF99']
for i in range(n_stacks):
values_stack = np.random.randint(10, 30, n_categories)
bars = ax.bar(range(n_categories), values_stack,
bottom=bottom, color=colors[i],
edgecolor='black', linewidth=1,
label=f'层{i+1}')
bottom += values_stack
ax.set_title('堆叠柱状图', fontsize=12)
ax.set_xticks(range(n_categories))
ax.set_xticklabels([f'类别{i+1}' for i in range(n_categories)])
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
# 5. 百分比堆叠柱状图
ax = axes[1, 1]
data = np.random.rand(3, 5) * 100 # 3组,5个类别
data_percent = data / data.sum(axis=0) * 100
bottom = np.zeros(5)
for i in range(3):
ax.bar(range(5), data_percent[i], bottom=bottom,
label=f'组{i+1}', alpha=0.8, edgecolor='black')
bottom += data_percent[i]
ax.set_title('百分比堆叠柱状图', fontsize=12)
ax.set_ylabel('百分比 (%)')
ax.set_xticks(range(5))
ax.set_xticklabels([f'类别{i+1}' for i in range(5)])
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
ax.set_ylim(0, 100)
# 6. 误差棒柱状图
ax = axes[1, 2]
means = np.random.randint(20, 80, 5)
errors = np.random.randint(5, 15, 5)
bars = ax.bar(range(5), means, yerr=errors,
capsize=5, color='lightgreen',
edgecolor='darkgreen', linewidth=2,
error_kw={'elinewidth': 2, 'ecolor': 'red'})
ax.set_title('误差棒柱状图', fontsize=12)
ax.set_xticks(range(5))
ax.set_xticklabels([f'组{i+1}' for i in range(5)])
ax.set_ylabel('均值 ± 误差')
ax.grid(True, alpha=0.3, axis='y')
plt.suptitle('柱状图家族', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()2. 高级柱状图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 1. 双向柱状图(旋风图)
ax = axes[0, 0]
categories = [f'项目{i+1}' for i in range(6)]
values_left = np.random.randint(10, 50, 6)
values_right = np.random.randint(10, 50, 6)
y_pos = np.arange(len(categories))
# 左侧柱状图(负值)
bars_left = ax.barh(y_pos, -values_left,
color='lightblue', edgecolor='navy',
label='左组')
# 右侧柱状图(正值)
bars_right = ax.barh(y_pos, values_right,
color='lightcoral', edgecolor='darkred',
label='右组')
# 添加数值标签
for bar, value in zip(bars_left, values_left):
ax.text(-value, bar.get_y() + bar.get_height()/2.,
f'{value}', ha='right', va='center')
for bar, value in zip(bars_right, values_right):
ax.text(value, bar.get_y() + bar.get_height()/2.,
f'{value}', ha='left', va='center')
ax.set_yticks(y_pos)
ax.set_yticklabels(categories)
ax.set_xlabel('数值')
ax.set_title('双向柱状图(旋风图)', fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3, axis='x')
# 隐藏顶部和右侧边框
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# 2. 直方分布柱状图
ax = axes[0, 1]
np.random.seed(42)
n_groups = 7
n_bars = 100
# 生成分组数据
data = []
for i in range(n_groups):
data.append(np.random.normal(loc=i*10, scale=3, size=n_bars))
# 绘制箱线图和散点
bp = ax.boxplot(data, positions=range(n_groups),
widths=0.6, patch_artist=True,
boxprops=dict(facecolor='lightblue'),
medianprops=dict(color='red', linewidth=2))
# 添加散点(抖动)
for i, group_data in enumerate(data):
x_jitter = np.random.normal(i, 0.05, len(group_data))
ax.scatter(x_jitter, group_data, alpha=0.4,
color='blue', s=20, edgecolor='none')
ax.set_title('箱线散点组合图', fontsize=12)
ax.set_xticks(range(n_groups))
ax.set_xticklabels([f'组{i+1}' for i in range(n_groups)])
ax.set_ylabel('数值')
ax.grid(True, alpha=0.3, axis='y')
# 3. 时间序列柱状图
ax = axes[1, 0]
import pandas as pd
# 生成时间序列数据
dates = pd.date_range('2023-01-01', periods=12, freq='MS')
values = np.random.randint(50, 200, 12)
bars = ax.bar(dates, values, width=20, # 宽度以天为单位
color='lightgreen', edgecolor='darkgreen',
alpha=0.8)
# 添加趋势线
from scipy import stats
x_num = dates.astype('int64') // 10**9 # 转换为秒数
slope, intercept, r_value, p_value, std_err = stats.linregress(x_num, values)
trend_line = slope * x_num + intercept
ax.plot(dates, trend_line, 'r--', linewidth=2,
label=f'趋势 (R²={r_value**2:.2f})')
ax.set_title('时间序列柱状图', fontsize=12)
ax.set_xlabel('日期')
ax.set_ylabel('数值')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.xticks(rotation=45)
# 4. 瀑布图
ax = axes[1, 1]
categories = ['起始', '收入', '成本', '税收', '其他', '最终']
values = [100, 30, -20, -10, 5, 0] # 最后一个是总计
# 计算累积值
cumulative = np.cumsum(values)
cumulative = np.insert(cumulative[:-1], 0, 0) # 起始累积为0
# 颜色:正值为绿,负值为红
colors = ['lightgray'] # 起始
colors.extend(['lightgreen' if v > 0 else 'lightcoral'
for v in values[1:-1]])
colors.append('gold') # 最终
# 绘制瀑布
for i in range(len(values)):
if i == 0: # 起始值
ax.bar(i, values[i], color=colors[i],
edgecolor='black', linewidth=1)
elif i == len(values) - 1: # 最终值
ax.bar(i, cumulative[-1], color=colors[i],
edgecolor='black', linewidth=2)
else: # 中间变化
ax.bar(i, values[i], bottom=cumulative[i],
color=colors[i], edgecolor='black', linewidth=1)
# 连接线
if i < len(values) - 1:
ax.plot([i + 0.5, i + 0.5],
[cumulative[i], cumulative[i] + values[i]],
'k-', linewidth=0.5)
# 添加数值标签
for i, (cat, val, cum) in enumerate(zip(categories, values, cumulative)):
if i == 0 or i == len(values) - 1:
y_pos = cum + val/2
else:
y_pos = cum + val/2
ax.text(i, y_pos, f'{val:+d}', ha='center', va='center',
fontweight='bold')
ax.set_title('瀑布图', fontsize=12)
ax.set_xticks(range(len(categories)))
ax.set_xticklabels(categories, rotation=45)
ax.set_ylabel('数值')
ax.grid(True, alpha=0.3, axis='y')
plt.suptitle('高级柱状图', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()五、统计图形家族
1. 直方图与分布
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
np.random.seed(42)
# 1. 基础直方图
ax = axes[0, 0]
data = np.random.randn(1000)
n, bins, patches = ax.hist(data, bins=30,
color='skyblue', edgecolor='black',
alpha=0.7)
ax.set_title('基础直方图', fontsize=12)
ax.set_xlabel('数值')
ax.set_ylabel('频数')
ax.grid(True, alpha=0.3)
# 2. 累积直方图
ax = axes[0, 1]
n, bins, patches = ax.hist(data, bins=30, cumulative=True,
color='lightcoral', edgecolor='darkred',
alpha=0.7)
ax.set_title('累积直方图', fontsize=12)
ax.set_xlabel('数值')
ax.set_ylabel('累积频数')
ax.grid(True, alpha=0.3)
# 3. 密度直方图
ax = axes[0, 2]
n, bins, patches = ax.hist(data, bins=30, density=True,
color='lightgreen', edgecolor='darkgreen',
alpha=0.7)
# 添加密度曲线
from scipy.stats import norm
x = np.linspace(data.min(), data.max(), 100)
ax.plot(x, norm.pdf(x, data.mean(), data.std()),
'r-', linewidth=2, label='正态分布')
ax.set_title('密度直方图', fontsize=12)
ax.set_xlabel('数值')
ax.set_ylabel('密度')
ax.legend()
ax.grid(True, alpha=0.3)
# 4. 堆叠直方图
ax = axes[1, 0]
data1 = np.random.normal(0, 1, 1000)
data2 = np.random.normal(2, 1.5, 800)
data3 = np.random.normal(-1, 0.8, 600)
ax.hist([data1, data2, data3], bins=30, stacked=True,
color=['skyblue', 'lightcoral', 'lightgreen'],
edgecolor='black', alpha=0.7,
label=['组1', '组2', '组3'])
ax.set_title('堆叠直方图', fontsize=12)
ax.set_xlabel('数值')
ax.set_ylabel('频数')
ax.legend()
ax.grid(True, alpha=0.3)
# 5. 2D直方图(热力图式)
ax = axes[1, 1]
x = np.random.randn(10000)
y = 0.5 * x + np.random.randn(10000)
# 方法1:hexbin
hb = ax.hexbin(x, y, gridsize=30, cmap='viridis')
ax.set_title('Hexbin 2D直方图', fontsize=12)
ax.set_xlabel('X')
ax.set_ylabel('Y')
plt.colorbar(hb, ax=ax)
# 6. 多组直方图比较
ax = axes[1, 2]
data_groups = []
for i in range(4):
data_groups.append(np.random.normal(loc=i, scale=0.5, size=500))
# 小提琴图样式的直方图
parts = ax.violinplot(data_groups, showmeans=True, showmedians=True)
for pc in parts['bodies']:
pc.set_facecolor('lightblue')
pc.set_edgecolor('black')
pc.set_alpha(0.7)
ax.set_title('多组分布比较(小提琴图)', fontsize=12)
ax.set_xticks(range(1, 5))
ax.set_xticklabels([f'组{i+1}' for i in range(4)])
ax.set_ylabel('数值')
ax.grid(True, alpha=0.3)
plt.suptitle('直方图与分布图形', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()2. 箱线图与小提琴图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
np.random.seed(42)
# 1. 基础箱线图
ax = axes[0, 0]
data = [np.random.randn(100) for _ in range(5)]
bp = ax.boxplot(data, patch_artist=True,
labels=[f'组{i+1}' for i in range(5)],
boxprops=dict(facecolor='lightblue'),
medianprops=dict(color='red', linewidth=2),
whiskerprops=dict(color='black'),
capprops=dict(color='black'),
flierprops=dict(marker='o', color='red', alpha=0.5))
# 添加均值点
means = [np.mean(d) for d in data]
ax.scatter(range(1, 6), means, color='green',
s=100, marker='D', label='均值', zorder=3)
ax.set_title('基础箱线图', fontsize=12)
ax.set_ylabel('数值')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
# 2. 分组箱线图
ax = axes[0, 1]
n_groups = 3
n_subgroups = 4
# 生成分组数据
all_data = []
positions = []
colors = ['lightblue', 'lightcoral', 'lightgreen']
for i in range(n_groups):
group_data = []
for j in range(n_subgroups):
data = np.random.normal(loc=i*2 + j*0.5, scale=1, size=100)
group_data.append(data)
positions.append(i * (n_subgroups + 1) + j + 1)
all_data.extend(group_data)
bp = ax.boxplot(all_data, positions=positions,
widths=0.6, patch_artist=True)
# 设置颜色
for i, box in enumerate(bp['boxes']):
box.set_facecolor(colors[i // n_subgroups])
box.set_alpha(0.7)
# 设置x轴标签
group_labels = []
for i in range(n_groups):
group_labels.append(f'组{i+1}')
group_labels.extend([''] * (n_subgroups - 1))
group_labels.append('')
ax.set_xticks([i * (n_subgroups + 1) + n_subgroups/2 for i in range(n_groups)])
ax.set_xticklabels([f'主要组{i+1}' for i in range(n_groups)])
ax.set_title('分组箱线图', fontsize=12)
ax.set_ylabel('数值')
ax.grid(True, alpha=0.3, axis='y')
# 3. 小提琴图
ax = axes[1, 0]
data = [np.random.randn(100) for _ in range(4)]
parts = ax.violinplot(data, showmeans=False, showmedians=True,
showextrema=True)
# 自定义小提琴图颜色
colors = ['lightblue', 'lightcoral', 'lightgreen', 'lightyellow']
for i, pc in enumerate(parts['bodies']):
pc.set_facecolor(colors[i])
pc.set_edgecolor('black')
pc.set_alpha(0.7)
# 自定义其他部分
parts['cmeans'].set_color('green')
parts['cmeans'].set_linewidth(2)
parts['cmedians'].set_color('red')
parts['cmedians'].set_linewidth(2)
parts['cmins'].set_color('black')
parts['cmaxes'].set_color('black')
parts['cbars'].set_color('black')
ax.set_xticks(range(1, 5))
ax.set_xticklabels([f'组{i+1}' for i in range(4)])
ax.set_title('小提琴图', fontsize=12)
ax.set_ylabel('数值')
ax.grid(True, alpha=0.3, axis='y')
# 4. 箱线图与小提琴图组合
ax = axes[1, 1]
data = [np.random.randn(100) for _ in range(4)]
# 创建双Y轴
ax2 = ax.twinx()
# 左侧:箱线图
bp = ax.boxplot(data, positions=np.arange(4)-0.15,
widths=0.3, patch_artist=True,
boxprops=dict(facecolor='lightblue', alpha=0.7),
medianprops=dict(color='red', linewidth=2))
# 右侧:小提琴图
vp = ax2.violinplot(data, positions=np.arange(4)+0.15,
showmeans=False, showmedians=False,
showextrema=False)
for pc in vp['bodies']:
pc.set_facecolor('lightcoral')
pc.set_edgecolor('black')
pc.set_alpha(0.7)
# 设置坐标轴
ax.set_xticks(range(4))
ax.set_xticklabels([f'组{i+1}' for i in range(4)])
ax.set_ylabel('箱线图 - 数值', color='blue')
ax2.set_ylabel('小提琴图 - 密度', color='red')
ax.tick_params(axis='y', labelcolor='blue')
ax2.tick_params(axis='y', labelcolor='red')
ax.set_title('箱线图与小提琴图组合', fontsize=12)
ax.grid(True, alpha=0.3, axis='y')
plt.suptitle('统计分布图形', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()六、特殊二维图形
1. 饼图与环形图
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 1. 基础饼图
ax = axes[0, 0]
sizes = [30, 25, 20, 15, 10]
labels = ['A', 'B', 'C', 'D', 'E']
colors = ['#FF9999', '#66B2FF', '#99FF99', '#FFCC99', '#FF99CC']
wedges, texts, autotexts = ax.pie(sizes, labels=labels, colors=colors,
autopct='%1.1f%%', startangle=90)
# 美化
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontweight('bold')
ax.set_title('基础饼图', fontsize=12)
# 2. 突出显示饼图
ax = axes[0, 1]
explode = (0.1, 0, 0, 0, 0) # 突出第一块
wedges, texts, autotexts = ax.pie(sizes, labels=labels, colors=colors,
explode=explode, autopct='%1.1f%%',
shadow=True, startangle=90)
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontweight('bold')
ax.set_title('突出显示饼图', fontsize=12)
# 3. 环形图
ax = axes[0, 2]
wedges, texts, autotexts = ax.pie(sizes, labels=labels, colors=colors,
autopct='%1.1f%%', startangle=90,
wedgeprops=dict(width=0.3))
# 添加中心圆
centre_circle = plt.Circle((0, 0), 0.70, fc='white')
ax.add_artist(centre_circle)
for autotext in autotexts:
autotext.set_color('black')
ax.set_title('环形图', fontsize=12)
# 4. 嵌套饼图
ax = axes[1, 0]
# 外层
outer_sizes = [40, 30, 20, 10]
outer_labels = ['外层A', '外层B', '外层C', '外层D']
outer_colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']
# 内层(细分)
inner_sizes = [15, 10, 8, 7, 12, 10, 8, 10, 10, 10]
inner_labels = [f'内{i+1}' for i in range(10)]
inner_colors = plt.cm.Set3(np.linspace(0, 1, 10))
# 绘制嵌套饼图
size = 0.3
ax.pie(outer_sizes, radius=1, colors=outer_colors,
labels=outer_labels, wedgeprops=dict(width=size, edgecolor='w'))
ax.pie(inner_sizes, radius=1-size, colors=inner_colors,
wedgeprops=dict(width=size, edgecolor='w'))
ax.set_title('嵌套饼图', fontsize=12)
# 5. 旭日图(Sunburst)
ax = axes[1, 1]
# 层次数据
hierarchical_sizes = [30, 20, 15, 10, 5, 5, 5, 5, 5]
hierarchical_labels = ['A', 'A1', 'A2', 'B', 'B1', 'B2', 'C', 'C1', 'C2']
hierarchical_colors = ['#FF9999', '#FFCCCC', '#FFDDDD',
'#66B2FF', '#CCDDFF', '#DDE8FF',
'#99FF99', '#CCFFCC', '#DDFFDD']
# 创建自定义wedge
from matplotlib.patches import Wedge
radius = 1
current_angle = 90
for size, label, color in zip(hierarchical_sizes,
hierarchical_labels,
hierarchical_colors):
# 根据标签级别确定半径
level = 1 if label[0] in ['A', 'B', 'C'] else 2
inner_radius = 0 if level == 1 else 0.5
outer_radius = 0.5 if level == 1 else 1
wedge = Wedge((0, 0), outer_radius,
current_angle, current_angle + size*360/100,
width=outer_radius-inner_radius,
facecolor=color, edgecolor='white')
ax.add_patch(wedge)
# 添加标签
mid_angle = current_angle + size*360/200
mid_radius = (inner_radius + outer_radius) / 2
x = mid_radius * np.cos(np.radians(mid_angle))
y = mid_radius * np.sin(np.radians(mid_angle))
ax.text(x, y, label, ha='center', va='center',
fontsize=10 if level == 1 else 8)
current_angle += size*360/100
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(-1.1, 1.1)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title('旭日图', fontsize=12)
# 6. 玫瑰图(极坐标饼图)
ax = axes[1, 2]
ax = plt.subplot(2, 3, 6, projection='polar')
categories = 12
values = np.random.randint(5, 20, categories)
theta = np.linspace(0, 2*np.pi, categories, endpoint=False)
width = 2*np.pi / categories
bars = ax.bar(theta, values, width=width,
color=plt.cm.viridis(values/values.max()),
edgecolor='white', linewidth=1)
# 添加标签
for i, (angle, value) in enumerate(zip(theta, values)):
rotation = np.degrees(angle)
if rotation < 90 or rotation > 270:
ha = 'left'
else:
ha = 'right'
rotation += 180
ax.text(angle, value + 1, f'{value}',
ha=ha, va='center', rotation=rotation,
fontsize=9)
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_title('玫瑰图(极坐标柱状图)', fontsize=12, pad=20)
ax.grid(True, alpha=0.3)
plt.suptitle('饼图与环形图家族', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()2. 等高线与热力图
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 生成数据
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y) + np.exp(-(X**2 + Y**2)/4)
# 1. 基础等高线
ax = axes[0, 0]
contour = ax.contour(X, Y, Z, levels=20, cmap='viridis')
ax.clabel(contour, inline=True, fontsize=8)
ax.set_title('基础等高线', fontsize=12)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_aspect('equal')
# 2. 填充等高线
ax = axes[0, 1]
contourf = ax.contourf(X, Y, Z, levels=20, cmap='viridis')
ax.set_title('填充等高线', fontsize=12)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_aspect('equal')
plt.colorbar(contourf, ax=ax)
# 3. 组合等高线
ax = axes[0, 2]
# 填充背景
contourf = ax.contourf(X, Y, Z, levels=20, cmap='viridis', alpha=0.7)
# 等高线
contour = ax.contour(X, Y, Z, levels=20, colors='black', linewidths=0.5)
ax.clabel(contour, inline=True, fontsize=8)
ax.set_title('组合等高线', fontsize=12)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_aspect('equal')
plt.colorbar(contourf, ax=ax)
# 4. 基础热力图
ax = axes[1, 0]
im = ax.imshow(Z, extent=[-3, 3, -3, 3],
origin='lower', cmap='hot', aspect='auto')
ax.set_title('基础热力图', fontsize=12)
ax.set_xlabel('X')
ax.set_ylabel('Y')
plt.colorbar(im, ax=ax)
# 5. 相关性热力图
ax = axes[1, 1]
np.random.seed(42)
n_vars = 8
data = np.random.randn(100, n_vars)
# 添加一些相关性
for i in range(1, n_vars):
data[:, i] = 0.7*data[:, i-1] + 0.3*np.random.randn(100)
corr_matrix = np.corrcoef(data.T)
im = ax.imshow(corr_matrix, cmap='coolwarm',
vmin=-1, vmax=1, aspect='auto')
# 添加数值
for i in range(n_vars):
for j in range(n_vars):
text = ax.text(j, i, f'{corr_matrix[i, j]:.2f}',
ha='center', va='center',
color='white' if abs(corr_matrix[i, j]) > 0.5 else 'black')
ax.set_xticks(range(n_vars))
ax.set_yticks(range(n_vars))
ax.set_xticklabels([f'Var{i+1}' for i in range(n_vars)])
ax.set_yticklabels([f'Var{i+1}' for i in range(n_vars)])
ax.set_title('相关性热力图', fontsize=12)
plt.colorbar(im, ax=ax)
# 6. 3D表面投影
from mpl_toolkits.mplot3d import Axes3D
ax = axes[1, 2]
ax.remove()
ax = fig.add_subplot(2, 3, 6, projection='3d')
# 创建3D表面
surf = ax.plot_surface(X, Y, Z, cmap='viridis',
alpha=0.8, linewidth=0.5,
antialiased=True)
# 添加等高线投影
ax.contour(X, Y, Z, zdir='z', offset=Z.min(),
cmap='viridis', alpha=0.5)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('3D表面与等高线投影', fontsize=12)
fig.colorbar(surf, ax=ax, shrink=0.6)
plt.suptitle('等高线与热力图', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()3. 特殊科学图形
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 1. 矢量场图
ax = axes[0, 0]
x = np.linspace(-2, 2, 10)
y = np.linspace(-2, 2, 10)
X, Y = np.meshgrid(x, y)
# 矢量场:径向向外
U = X # x方向分量
V = Y # y方向分量
# 计算矢量长度用于着色
M = np.hypot(U, V)
q = ax.quiver(X, Y, U, V, M, cmap='viridis',
angles='xy', scale_units='xy', scale=5)
ax.set_title('矢量场图', fontsize=12)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_aspect('equal')
plt.colorbar(q, ax=ax)
# 2. 流线图
ax = axes[0, 1]
x = np.linspace(-3, 3, 50)
y = np.linspace(-3, 3, 50)
X, Y = np.meshgrid(x, y)
# 创建一个旋涡场
U = -Y # x方向分量
V = X # y方向分量
strm = ax.streamplot(X, Y, U, V, color=M,
linewidth=1, cmap='viridis',
arrowsize=1, density=1.5)
ax.set_title('流线图', fontsize=12)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_aspect('equal')
plt.colorbar(strm.lines, ax=ax)
# 3. 极坐标图
ax = axes[0, 2]
ax = plt.subplot(2, 3, 3, projection='polar')
theta = np.linspace(0, 2*np.pi, 100)
r = 1 + 0.5 * np.sin(5*theta)
ax.plot(theta, r, linewidth=2)
ax.fill(theta, r, alpha=0.3)
ax.set_title('极坐标线图', fontsize=12, pad=20)
ax.grid(True, alpha=0.3)
# 4. 雷达图
ax = axes[1, 0]
categories = ['A', 'B', 'C', 'D', 'E', 'F']
N = len(categories)
values = np.random.randint(1, 10, N)
# 重复第一个值以闭合图形
values = np.concatenate((values, [values[0]]))
angles = np.linspace(0, 2*np.pi, N, endpoint=False).tolist()
angles += angles[:1]
ax = plt.subplot(2, 3, 4, projection='polar')
ax.plot(angles, values, 'o-', linewidth=2)
ax.fill(angles, values, alpha=0.25)
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)
ax.set_title('雷达图', fontsize=12, pad=20)
ax.grid(True, alpha=0.3)
# 5. 面积图
ax = axes[1, 1]
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
ax.fill_between(x, y1, alpha=0.5, label='sin(x)', color='blue')
ax.fill_between(x, y2, alpha=0.5, label='cos(x)', color='red')
ax.fill_between(x, y1, y2, where=(y1 > y2),
alpha=0.3, color='green', label='sin > cos')
ax.set_title('面积图', fontsize=12)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.legend()
ax.grid(True, alpha=0.3)
# 6. 词云图(简化版)
ax = axes[1, 2]
from wordcloud import WordCloud # 需要安装:pip install wordcloud
# 示例文本
text = """
Python Matplotlib 数据可视化 图表 图形 折线图 散点图 柱状图 饼图
热力图 等高线 箱线图 小提琴图 面积图 雷达图 极坐标 科学计算
数据分析 机器学习 深度学习 人工智能 编程 开发 代码 算法
"""
# 生成词云
wordcloud = WordCloud(width=400, height=300,
background_color='white',
colormap='viridis').generate(text)
ax.imshow(wordcloud, interpolation='bilinear')
ax.axis('off')
ax.set_title('词云图', fontsize=12)
plt.suptitle('特殊科学图形', fontsize=16, y=0.98)
plt.tight_layout()
plt.show()图形选择指南
# 图形选择决策树
selection_guide = {
'比较数据': {
'少量类别': '柱状图',
'多类别': '分组柱状图',
'随时间变化': '折线图',
'构成比例': '饼图/环形图',
},
'分布数据': {
'单变量': '直方图/密度图',
'多变量': '散点图/箱线图',
'统计摘要': '箱线图/小提琴图',
'二维密度': '等高线/热力图',
},
'关系数据': {
'两个变量': '散点图',
'多个变量': '散点图矩阵',
'相关性': '热力图',
'时间序列': '折线图',
},
'空间数据': {
'地理分布': '等高线',
'矢量场': '矢量图/流线图',
'极坐标': '雷达图/玫瑰图',
},
}
# 最佳实践建议
best_practices = """
1. 折线图:用于展示趋势和变化,特别是时间序列数据
2. 柱状图:用于比较不同类别的数值,特别是分类数据
3. 散点图:用于展示两个变量之间的关系和分布
4. 饼图:用于展示构成比例,但类别不宜过多(≤6)
5. 热力图:用于展示矩阵数据或相关性
6. 箱线图:用于展示统计分布和异常值
7. 直方图:用于展示单变量分布
8. 等高线:用于展示二维函数或地形
"""
print("图形选择指南:")
for category, options in selection_guide.items():
print(f"\n{category}:")
for situation, graph in options.items():
print(f" • {situation}: {graph}")综合实战示例
# 创建综合仪表板
fig = plt.figure(figsize=(16, 12))
# 使用GridSpec创建复杂布局
gs = fig.add_gridspec(3, 4, hspace=0.3, wspace=0.3)
# 1. 时间序列折线图(主图)
ax1 = fig.add_subplot(gs[0, :2])
dates = pd.date_range('2023-01-01', periods=100, freq='D')
values = 100 + np.cumsum(np.random.randn(100))
volume = np.random.randint(1000, 5000, 100)
ax1.plot(dates, values, 'b-', linewidth=2, label='价格')
ax1.set_title('📈 股票价格走势', fontsize=14, fontweight='bold')
ax1.set_ylabel('价格 (USD)', color='blue')
ax1.tick_params(axis='y', labelcolor='blue')
ax1.grid(True, alpha=0.3)
ax1.legend(loc='upper left')
# 添加交易量(次坐标轴)
ax1_vol = ax1.twinx()
ax1_vol.fill_between(dates, 0, volume, alpha=0.3, color='gray', label='交易量')
ax1_vol.set_ylabel('交易量', color='gray')
ax1_vol.tick_params(axis='y', labelcolor='gray')
# 2. 相关性热力图
ax2 = fig.add_subplot(gs[0, 2:])
np.random.seed(42)
assets = 6
corr_data = np.random.randn(100, assets)
corr_matrix = np.corrcoef(corr_data.T)
im = ax2.imshow(corr_matrix, cmap='coolwarm', vmin=-1, vmax=1)
ax2.set_title('📊 资产相关性矩阵', fontsize=14, fontweight='bold')
ax2.set_xticks(range(assets))
ax2.set_yticks(range(assets))
ax2.set_xticklabels([f'资产{i+1}' for i in range(assets)])
ax2.set_yticklabels([f'资产{i+1}' for i in range(assets)])
# 添加相关系数
for i in range(assets):
for j in range(assets):
text = ax2.text(j, i, f'{corr_matrix[i, j]:.2f}',
ha='center', va='center',
color='white' if abs(corr_matrix[i, j]) > 0.5 else 'black',
fontsize=9)
plt.colorbar(im, ax=ax2)
# 3. 收益率分布直方图
ax3 = fig.add_subplot(gs[1, :2])
returns = np.diff(values) / values[:-1]
ax3.hist(returns, bins=30, density=True,
color='lightgreen', edgecolor='darkgreen', alpha=0.7)
# 添加正态分布曲线
from scipy.stats import norm
x = np.linspace(returns.min(), returns.max(), 100)
ax3.plot(x, norm.pdf(x, returns.mean(), returns.std()),
'r-', linewidth=2, label='正态分布')
ax3.axvline(x=returns.mean(), color='blue', linestyle='--',
label=f'均值: {returns.mean():.3%}')
ax3.axvline(x=returns.mean() + returns.std(), color='orange',
linestyle=':', alpha=0.5)
ax3.axvline(x=returns.mean() - returns.std(), color='orange',
linestyle=':', alpha=0.5)
ax3.set_title('📊 收益率分布', fontsize=14, fontweight='bold')
ax3.set_xlabel('收益率')
ax3.set_ylabel('密度')
ax3.legend()
ax3.grid(True, alpha=0.3)
# 4. 资产配置饼图
ax4 = fig.add_subplot(gs[1, 2:])
sectors = ['科技', '金融', '医疗', '能源', '消费', '其他']
weights = [35, 25, 15, 10, 10, 5]
colors = ['#FF6B6B', '#4ECDC4', '#FFD166', '#06D6A0', '#118AB2', '#EF476F']
wedges, texts, autotexts = ax4.pie(weights, labels=sectors, colors=colors,
autopct='%1.1f%%', startangle=90,
wedgeprops=dict(edgecolor='w', linewidth=2))
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontweight('bold')
ax4.set_title('📈 行业配置比例', fontsize=14, fontweight='bold')
# 5. 滚动统计箱线图
ax5 = fig.add_subplot(gs[2, :])
window_sizes = [5, 10, 20, 30, 60]
rolling_data = []
for window in window_sizes:
rolling_returns = pd.Series(returns).rolling(window).std().dropna()
rolling_data.append(rolling_returns.values)
bp = ax5.boxplot(rolling_data, patch_artist=True,
labels=[f'{w}天' for w in window_sizes],
boxprops=dict(facecolor='lightblue', alpha=0.7),
medianprops=dict(color='red', linewidth=2))
ax5.set_title('📊 滚动波动率统计', fontsize=14, fontweight='bold')
ax5.set_ylabel('波动率')
ax5.set_xlabel('滚动窗口大小')
ax5.grid(True, alpha=0.3, axis='y')
# 添加散点显示数据点
for i, data in enumerate(rolling_data):
x_jitter = np.random.normal(i+1, 0.05, len(data))
ax5.scatter(x_jitter, data, alpha=0.3, color='blue', s=10, edgecolor='none')
# 添加总标题
fig.suptitle('投资组合分析仪表板', fontsize=18, fontweight='bold', y=0.98)
# 添加脚注
fig.text(0.5, 0.01,
'数据来源:模拟数据 | 最后更新:2023-12-31',
ha='center', fontsize=10, style='italic',
bbox=dict(boxstyle='round', facecolor='lightgray', alpha=0.3))
plt.tight_layout(rect=[0, 0.03, 1, 0.97])
plt.show()通过掌握这些二维图形,你可以为各种数据可视化需求选择合适的图表类型,并创建专业、美观的可视化作品。
