如何在Seaborn热力图上正确叠加适配对数轴的理论曲线
如何在Seaborn热力图上正确叠加适配对数轴的理论曲线
你遇到的问题本质是热力图的x轴是离散分类轴——虽然你的num_trials/vals是用对数生成的数值,但当你把它们作为热力图的列时,Seaborn会把这些数值当作分类标签渲染,x轴的每个刻度对应一个分类位置(从0、1、2…依次递增),而非原始的数值大小。而你直接用原始数值作为sns.lineplot的x坐标时,Matplotlib会把这些数值当作绝对坐标,和热力图的分类轴完全不匹配,所以曲线才会出现错位。
下面我从你简化的y=1/sqrt(x)例子入手,一步步解决这个问题,搞定这个案例后,你的Sharpe Ratio问题就能直接复用逻辑。
解决步骤
1. 核心问题拆解
热力图的x轴是分类变量:每个num_trials/vals值对应一个固定的分类位置(比如第1个列对应x=0,第2个对应x=1…),和数值本身的大小无关。而理论曲线的x用的是原始数值(比如10、100、1000…),这两个坐标系统完全不兼容,必须做一次映射转换。
2. 修正后的简化版代码(y=1/sqrt(x)案例)
我在代码里标注了关键的映射逻辑,替换你原来的曲线绘制部分即可:
import numpy as np import pandas as pd import scipy.stats as stats import matplotlib.pyplot as plt import seaborn as sns vals = np.logspace(0.5, 3.5, 100).astype(int) theoretical_values = pd.Series(1/np.sqrt(vals), index=vals) num_runs = 10000 trials_per_run = 10 exprimental_values = np.zeros(shape=(num_runs * len(vals), 2)) for i, n in enumerate(vals): for j in range(num_runs): dist = stats.norm.rvs(size=(trials_per_run, n)).mean(axis=1) exprimental_values[num_runs * i + j, 0] = n exprimental_values[num_runs * i + j, 1] = np.std(dist, ddof=1) exprimental_values = pd.DataFrame(exprimental_values, columns=['num', 'std']) heatmap_df = exprimental_values.copy() heatmap_df['count'] = 1 heatmap_df['std'] = heatmap_df['std'].round(3) heatmap_df = heatmap_df.groupby(['num', 'std'])['count'].sum().reset_index() heatmap_df = heatmap_df.pivot(index='std', columns='num', values='count') heatmap_df = heatmap_df.fillna(0) heatmap_df = heatmap_df.div(heatmap_df.sum(axis=0), axis=1) heatmap_df = heatmap_df.sort_index(ascending=False) fig, ax = plt.subplots() # 先绘制热力图 sns.heatmap(heatmap_df, cmap='Blues', ax=ax) # --- 关键:坐标映射修正 --- # 1. 获取热力图x轴的分类标签,转成数值类型 heatmap_x_labels = [float(label.get_text()) for label in ax.get_xticklabels()] # 2. 把理论曲线的x值(vals)映射到热力图x轴的分类位置 x_positions = [heatmap_x_labels.index(val) for val in vals] # 3. 用映射后的位置绘制曲线,确保和热力图x轴对齐 sns.lineplot( x=x_positions, y=theoretical_values.values, ax=ax, linestyle='dashed', color='crimson', label=r'$y=1/\sqrt{x}$' ) # 优化显示:旋转x轴标签避免重叠 ax.set_xticklabels(ax.get_xticklabels(), rotation=45) plt.legend() plt.tight_layout() plt.show()
3. 迁移到你的Sharpe Ratio问题
只需要把同样的映射逻辑套用到你的原始代码中,替换原来的sns.lineplot部分:
# 绘制热力图后添加以下代码 sns.heatmap(heatmap_df, cmap = 'Blues', ax = ax) # --- 坐标映射修正 --- heatmap_x_labels = [int(label.get_text()) for label in ax.get_xticklabels()] x_positions = [heatmap_x_labels.index(num) for num in trials_per_sim_list] # 绘制对齐后的理论曲线 sns.lineplot( x=x_positions, y=sharpe_ratio_theoretical['max{SR}'].values, linestyle='dashed', ax=ax, color='crimson', label='Theoretical Max SR' ) plt.show()
额外优化建议
如果希望x轴显示为更直观的对数刻度样式(比如10^1、10^2),可以在绘制完热力图后添加:
from matplotlib.ticker import LogFormatter ax.set_xticklabels([f'$10^{int(np.log10(float(label.get_text())))}' for label in ax.get_xticklabels()])
这样处理后,理论曲线就能完美和热力图的轴对齐,不会再出现错位的情况了。
备注:内容来源于stack exchange,提问作者Charles0349
相关产品推荐
相关产品推荐

