如何基于两个含重复日期的DataFrame绘制苹果股价与地震事件关联图表并解决标记重叠问题
嘿,我来帮你搞定这个需求!咱们可以用Plotly的Scatter轨迹高效添加地震标记,同时通过数据关联和偏移处理解决重叠问题,一步一步来:
核心思路
- 关联地震与当日股价:通过合并两个DataFrame,让每个地震事件匹配到当日的苹果收盘价,这样标记就能精准定位到股价位置。
- 解决单日多地震重叠:给同日期的地震标记添加微小的y轴偏移,让它们在收盘价上下均匀分布,既不偏离股价太多,又能清晰区分每个事件。
- 高效渲染:用单个Scatter轨迹批量添加所有标记,避免循环添加的低效问题。
完整实现代码
import pandas as pd import plotly.graph_objects as go if __name__ == '__main__': # 加载数据 df_apple_stock = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv') df_earthquakes = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/earthquakes-23k.csv') # 统一日期格式为UTC df_apple_stock['Date'] = pd.to_datetime(df_apple_stock['Date'], utc=True) df_earthquakes['Date'] = pd.to_datetime(df_earthquakes['Date'], utc=True) # 筛选2015-2016年的地震数据 start_day = pd.to_datetime('02/17/2015', utc=True) end_day = pd.to_datetime('12/31/2016', utc=True) df_earthquakes = df_earthquakes[df_earthquakes['Date'].between(start_day, end_day)] # --- 关键步骤1:关联地震与当日股价 --- # 只保留股票数据中的日期和收盘价,用于合并 stock_price_map = df_apple_stock[['Date', 'AAPL.Close']] # 合并地震数据与股价数据,只保留有对应股价的地震(过滤周末/节假日的地震) merged_df = pd.merge(df_earthquakes, stock_price_map, on='Date', how='inner') # --- 关键步骤2:处理单日多地震的偏移 --- # 计算每个日期的地震数量 merged_df['daily_count'] = merged_df.groupby('Date')['Date'].transform('count') # 标记每个地震在当日的顺序 merged_df['day_index'] = merged_df.groupby('Date').cumcount() # 设置偏移步长(这里用收盘价的0.5%,避免偏移过大) offset_step = merged_df['AAPL.Close'] * 0.005 # 计算每个标记的y轴偏移:让同日期的标记在收盘价上下对称分布 merged_df['y_offset'] = (merged_df['day_index'] - (merged_df['daily_count'] - 1)/2) * offset_step # 最终标记的y坐标 = 当日收盘价 + 偏移 merged_df['marker_y'] = merged_df['AAPL.Close'] + merged_df['y_offset'] # --- 绘制股价线 --- fig = go.Figure(data=[go.Scatter( x=df_apple_stock['Date'], y=df_apple_stock['AAPL.Close'], mode='lines', name='AAPL Close Price', hovertemplate= '<b>%{x}</b><br>' + 'Open: $%{customdata[1]:.2f}<br>' + 'Close: $%{y:.2f}<br>' + 'High: $%{customdata[2]:.2f}<br>' + 'Low: $%{customdata[3]:.2f}<br>' + 'Volume: %{customdata[5]:,}', customdata=df_apple_stock.values )]) # --- 添加地震标记 --- fig.add_trace(go.Scatter( x=merged_df['Date'], y=merged_df['marker_y'], mode='markers', name='Earthquakes', marker=dict( color='#ff4444', size=merged_df['Magnitude'] * 2, # 震级越大,标记越大,直观区分 symbol='triangle-up', opacity=0.8 ), hovertemplate='<b>Earthquake Event</b><br>' + 'Date: %{x}<br>' + 'Magnitude: %{customdata[0]}<br>' + 'Latitude: %{customdata[1]:.4f}<br>' + 'Longitude: %{customdata[2]:.4f}<br>' + '当日收盘价: $%{customdata[3]:.2f}', customdata=merged_df[['Magnitude', 'Latitude', 'Longitude', 'AAPL.Close']].values )) # --- 优化布局 --- fig.update_layout( title='AAPL Stock Price with Earthquake Markers (2015-2016)', xaxis_title='Date', yaxis_title='AAPL Close Price ($)', hovermode='x unified', # 鼠标悬停时显示同一日期的所有信息 legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1) ) fig.show()
关键细节解释
- 数据关联:通过
pd.merge将地震数据和股票收盘价绑定,确保每个地震标记都能对应到当日的股价位置,解决了你之前无法关联y值的问题。 - 重叠处理:
- 先统计每个日期的地震数量,再给每个地震分配一个顺序索引。
- 用对称偏移的方式,让同日期的标记在收盘价上下均匀分布,比如当日有3个地震,偏移分别为-0.5%、0、+0.5%的收盘价,既避免重叠,又保持和股价的关联。
- 高效渲染:用单个Scatter轨迹一次性添加所有地震标记,比循环添加annotation高效得多,即使数据量大也能流畅运行。
- 交互优化:
- 标记大小和震级挂钩,直观展示地震强度。
- 悬停模板包含所有关键信息,
hovermode='x unified'让你在查看股价时同时看到当日所有地震事件。
可选优化
- 如果觉得偏移量太大/太小,可以调整
offset_step的比例(比如把0.005改成0.003或0.007)。 - 对于极端大的地震,可以用不同的标记颜色或符号突出显示。
- 如果想过滤掉小震级的事件,可以添加
merged_df = merged_df[merged_df['Magnitude'] >= 5]这样的筛选条件。
内容的提问来源于stack exchange,提问作者J. Doe
相关产品推荐
相关产品推荐

