一、Plotly简介与生态系统
import plotly.express as pximport plotly.graph_objects as goimport plotly.io as pioimport plotly.subplots as spfrom plotly.offline import init_notebook_mode, iplotimport pandas as pdimport numpy as npimport warningswarnings.filterwarnings('ignore')# 初始化Jupyter Notebook模式(如果在本地运行)try: init_notebook_mode(connected=True) print("Plotly Notebook模式已激活")except: print("Plotly在线模式")# Plotly生态系统概览"""1. plotly.py - Python图形库(今日重点)2. plotly.js - JavaScript图形库3. Dash - 基于Plotly的Web应用框架4. Chart Studio - 在线图表创建和分享平台5. Kaleido - 静态图像导出引擎"""print(f"Plotly版本: {pio.__version__}")
二、Plotly基础配置与模板
# 1. 配置与模板print("可用模板:", list(pio.templates.keys()))# 设置默认模板pio.templates.default = "plotly_white"# 自定义模板custom_template = go.layout.Template( layout=go.Layout( font=dict(family="Arial", size=12), title=dict(font=dict(size=20, color="#2c3e50")), plot_bgcolor="white", paper_bgcolor="#f8f9fa", xaxis=dict( gridcolor="lightgray", gridwidth=1, showline=True, linecolor="gray", linewidth=2 ), yaxis=dict( gridcolor="lightgray", gridwidth=1, showline=True, linecolor="gray", linewidth=2 ), colorway=px.colors.qualitative.Set3 ))pio.templates["custom"] = custom_templatepio.templates.default = "custom"# 2. 渲染器设置# 离线渲染(保存为HTML)pio.renderers.default = "browser" # 在浏览器中打开# pio.renderers.default = "png" # 保存为PNG# pio.renderers.default = "jupyterlab" # JupyterLab中显示
三、Plotly Express快速入门
1. 基础图表类型
# 创建示例数据np.random.seed(42)n_points = 100data = pd.DataFrame({ 'X': np.random.randn(n_points).cumsum(), 'Y': np.random.randn(n_points).cumsum(), 'Category': np.random.choice(['A', 'B', 'C'], n_points), 'Size': np.random.uniform(10, 100, n_points), 'Value': np.random.uniform(0, 1, n_points)})# 1. 散点图fig1 = px.scatter( data, x='X', y='Y', color='Category', # 颜色分组 size='Size', # 大小映射 hover_name='Category', # 悬停显示 hover_data=['Value'], # 悬停数据 title='交互式散点图示例', template='plotly_white')fig1.update_layout( xaxis_title="X轴", yaxis_title="Y轴", legend_title="类别")fig1.show()# 2. 线图time_series = pd.DataFrame({ 'Date': pd.date_range('2023-01-01', periods=50, freq='D'), 'Value_A': np.sin(np.linspace(0, 10*np.pi, 50)) + np.random.randn(50)*0.2, 'Value_B': np.cos(np.sin(np.linspace(0, 10*np.pi, 50))) + np.random.randn(50)*0.2, 'Group': np.tile(['Group1', 'Group2'], 25)})fig2 = px.line( time_series, x='Date', y=['Value_A', 'Value_B'], color='Group', line_dash='Group', markers=True, title='时间序列线图', template='seaborn')# 添加范围选择器fig2.update_layout( xaxis=dict( rangeselector=dict( buttons=list([ dict(count=7, label="1周", step="day", stepmode="backward"), dict(count=1, label="1月", step="month", stepmode="backward"), dict(count=3, label="3月", step="month", stepmode="backward"), dict(step="all", label="全部") ]) ), rangeslider=dict(visible=True), type="date" ))fig2.show()
2. 高级Express图表
# 加载内置数据集tips = px.data.tips()iris = px.data.iris()gapminder = px.data.gapminder()# 创建多类型图表网格from plotly.subplots import make_subplotsfig = make_subplots( rows=2, cols=2, subplot_titles=('散点矩阵', '平行坐标', '旭日图', '密度热力图'), specs=[ [{'type': 'splom'}, {'type': 'parcoords'}], [{'type': 'sunburst'}, {'type': 'densitymapbox'}] ])# 1. 散点矩阵scatter_matrix = px.scatter_matrix( iris, dimensions=["sepal_length", "sepal_width", "petal_length", "petal_width"], color="species", title="鸢尾花特征散点矩阵")# 2. 平行坐标图parallel = px.parallel_coordinates( iris, color="sepal_length", dimensions=["sepal_width", "petal_length", "petal_width", "species_id"], color_continuous_scale=px.colors.diverging.Tealrose, labels={"species_id": "物种ID"})# 3. 旭日图sunburst_data = pd.DataFrame({ 'labels': ['中国', '北京', '上海', '广东', '广州', '深圳', '美国', '加州', '纽约'], 'parents': ['', '中国', '中国', '中国', '广东', '广东', '', '美国', '美国'], 'values': [100, 30, 25, 45, 20, 25, 80, 40, 40]})sunburst = px.sunburst( sunburst_data, names='labels', parents='parents', values='values', title="旭日图示例")# 4. 密度热力图(需要地图box)# 创建地图数据map_data = pd.DataFrame({ 'lat': np.random.uniform(30, 40, 100), 'lon': np.random.uniform(110, 120, 100), 'value': np.random.randn(100)})density_map = px.density_mapbox( map_data, lat='lat', lon='lon', z='value', radius=10, center=dict(lat=35, lon=115), zoom=5, mapbox_style="carto-positron", title="密度热力图")# 单独显示每个图表scatter_matrix.show()parallel.show()sunburst.show()density_map.show()
四、Plotly Graph Objects高级控制
1. 基础图形对象
# 创建复杂图表fig = go.Figure()# 添加多个轨迹fig.add_trace(go.Scatter( x=np.linspace(0, 10, 100), y=np.sin(np.linspace(0, 10, 100)), mode='lines+markers', name='正弦波', line=dict(color='firebrick', width=2, dash='dash'), marker=dict(size=6, symbol='circle-open')))fig.add_trace(go.Scatter( x=np.linspace(0, 10, 100), y=np.cos(np.linspace(0, 10, 100)), mode='lines', name='余弦波', line=dict(color='royalblue', width=3), fill='tozeroy', fillcolor='rgba(65, 105, 225, 0.3)'))fig.add_trace(go.Scatter( x=np.linspace(0, 10, 20), y=np.sin(np.linspace(0, 10, 20)) + np.random.randn(20)*0.2, mode='markers', name='带噪声数据', marker=dict( size=12, color=np.random.randn(20), colorscale='Viridis', showscale=True, colorbar=dict(title="颜色值") )))# 更新布局fig.update_layout( title='多轨迹图表', xaxis_title='X轴', yaxis_title='Y轴', hovermode='x unified', showlegend=True, legend=dict( yanchor="top", y=0.99, xanchor="left", x=0.01 ), annotations=[ dict( x=5, y=0, text="中心点", showarrow=True, arrowhead=2, ax=0, ay=-40 ) ])# 添加形状fig.add_shape( type="rect", x0=2, x1=8, y0=-0.5, y1=0.5, fillcolor="lightgray", opacity=0.5, line=dict(width=0))fig.show()
2. 3D可视化
# 创建3D图表fig = make_subplots( rows=2, cols=2, specs=[ [{'type': 'surface'}, {'type': 'mesh3d'}], [{'type': 'scatter3d'}, {'type': 'cone'}] ], subplot_titles=('3D曲面', '3D网格', '3D散点', '3D向量场'))# 1. 3D曲面X = np.arange(-5, 5, 0.25)Y = np.arange(-5, 5, 0.25)X, Y = np.meshgrid(X, Y)R = np.sqrt(X**2 + Y**2)Z = np.sin(R)fig.add_trace( go.Surface( x=X, y=Y, z=Z, colorscale='Viridis', contours={ "x": {"show": True, "color": "white"}, "y": {"show": True, "color": "white"}, "z": {"show": True, "usecolormap": True} } ), row=1, col=1)# 2. 3D网格vertices = np.array([ [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]])triangles = np.array([ [0, 1, 2], [0, 2, 3], # 底面 [4, 5, 6], [4, 6, 7], # 顶面 [0, 1, 5], [0, 5, 4], # 前面 [1, 2, 6], [1, 6, 5], # 右面 [2, 3, 7], [2, 7, 6], # 后面 [3, 0, 4], [3, 4, 7] # 左面])fig.add_trace( go.Mesh3d( x=vertices[:, 0], y=vertices[:, 1], z=vertices[:, 2], i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2], color='lightblue', opacity=0.6 ), row=1, col=2)# 3. 3D散点图np.random.seed(42)n_points = 100scatter_3d_data = pd.DataFrame({ 'x': np.random.randn(n_points), 'y': np.random.randn(n_points), 'z': np.random.randn(n_points), 'value': np.random.randn(n_points), 'category': np.random.choice(['A', 'B', 'C'], n_points)})fig.add_trace( go.Scatter3d( x=scatter_3d_data['x'], y=scatter_3d_data['y'], z=scatter_3d_data['z'], mode='markers', marker=dict( size=5, color=scatter_3d_data['value'], colorscale='Rainbow', showscale=True ), text=scatter_3d_data['category'] ), row=2, col=1)# 4. 3D向量场(锥形图)X, Y, Z = np.mgrid[-1:1:5j, -1:1:5j, -1:1:5j]U = np.sin(np.pi * X) * np.cos(np.pi * Y) * np.cos(np.pi * Z)V = -np.cos(np.pi * X) * np.sin(np.pi * Y) * np.cos(np.pi * Z)W = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * X) * np.cos(np.pi * Y) * np.sin(np.pi * Z))fig.add_trace( go.Cone( x=X.flatten(), y=Y.flatten(), z=Z.flatten(), u=U.flatten(), v=V.flatten(), w=W.flatten(), sizemode="absolute", sizeref=0.3, colorscale='Blues', showscale=False ), row=2, col=2)fig.update_layout( title='3D可视化综合示例', height=800, showlegend=False)fig.show()
五、交互功能与动态图表
1. 交互式控件
# 创建带控件的交互式图表from datetime import datetime# 生成时间序列数据dates = pd.date_range('2023-01-01', '2023-12-31', freq='D')n_dates = len(dates)financial_data = pd.DataFrame({ 'Date': dates, 'Stock_A': 100 + np.cumsum(np.random.randn(n_dates) * 0.5), 'Stock_B': 150 + np.cumsum(np.random.randn(n_dates) * 0.8), 'Stock_C': 80 + np.cumsum(np.random.randn(n_dates) * 0.3), 'Volume_A': np.random.randint(1000, 10000, n_dates), 'Volume_B': np.random.randint(2000, 15000, n_dates), 'Volume_C': np.random.randint(500, 8000, n_dates)})# 创建主图表fig = go.Figure()# 添加股票价格轨迹for stock in ['Stock_A', 'Stock_B', 'Stock_C']: fig.add_trace(go.Scatter( x=financial_data['Date'], y=financial_data[stock], name=stock.replace('_', ' '), mode='lines', hovertemplate='<b>%{x}</b><br>价格: $%{y:.2f}<extra></extra>', visible=True ))# 添加成交量条形图(初始隐藏)for i, stock in enumerate(['Stock_A', 'Stock_B', 'Stock_C']): fig.add_trace(go.Bar( x=financial_data['Date'], y=financial_data[f'Volume_{stock.split("_")[1]}'], name=f'{stock.replace("_", " ")} 成交量', marker_color=px.colors.qualitative.Set3[i], opacity=0.6, visible=False, yaxis='y2' ))# 更新布局fig.update_layout( title='股票价格与成交量分析', xaxis=dict( title='日期', rangeselector=dict( buttons=list([ dict(count=1, label='1月', step='month', stepmode='backward'), dict(count=3, label='3月', step='month', stepmode='backward'), dict(count=6, label='6月', step='month', stepmode='backward'), dict(step='all', label='全部') ]) ), rangeslider=dict(visible=True), type='date' ), yaxis=dict( title='价格 ($)', side='left' ), yaxis2=dict( title='成交量', side='right', overlaying='y', showgrid=False, visible=False ), hovermode='x unified', legend=dict( orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1 ), updatemenus=[ dict( type='buttons', direction='right', x=0.3, y=1.15, showactive=True, buttons=list([ dict( label='显示价格', method='update', args=[{'visible': [True, True, True, False, False, False]}, {'yaxis2.visible': False}] ), dict( label='显示成交量', method='update', args=[{'visible': [False, False, False, True, True, True]}, {'yaxis2.visible': True}] ), dict( label='全部显示', method='update', args=[{'visible': [True]*6}, {'yaxis2.visible': True}] ) ]) ), dict( type='dropdown', direction='down', x=0.7, y=1.15, showactive=True, buttons=[ dict(label='线性', method='relayout', args=[{'yaxis.type': 'linear'}]), dict(label='对数', method='relayout', args=[{'yaxis.type': 'log'}]), dict(label='百分比变化', method='update', args=[ {'y': [financial_data['Stock_A'].pct_change() * 100, financial_data['Stock_B'].pct_change() * 100, financial_data['Stock_C'].pct_change() * 100]}, {'yaxis.title.text': '变化百分比 (%)'} ]) ] ) ])# 添加形状和注释fig.add_shape( type='line', x0=financial_data['Date'].iloc[0], x1=financial_data['Date'].iloc[-1], y0=100, y1=100, line=dict(color='red', width=2, dash='dash'))fig.add_annotation( x=financial_data['Date'].iloc[30], y=100, text='支撑位', showarrow=True, arrowhead=2)fig.show()
2. 动画图表
# 创建动画图表# 生成模拟数据np.random.seed(42)n_frames = 50n_points = 100# 创建基础数据base_data = pd.DataFrame({ 'x': np.random.randn(n_points), 'y': np.random.randn(n_points), 'size': np.random.uniform(10, 50, n_points), 'category': np.random.choice(['A', 'B', 'C', 'D'], n_points)})# 创建动画帧frames = []for i in range(n_frames): # 每帧稍微移动数据点 frame_data = base_data.copy() frame_data['x'] = frame_data['x'] + np.sin(i * 0.1) * 0.5 frame_data['y'] = frame_data['y'] + np.cos(i * 0.1) * 0.5 frame_data['size'] = frame_data['size'] * (1 + 0.1 * np.sin(i * 0.2)) frames.append( go.Frame( data=[ go.Scatter( x=frame_data['x'], y=frame_data['y'], mode='markers', marker=dict( size=frame_data['size'], color=frame_data['category'], colorscale='Rainbow', showscale=True ) ) ], name=f'frame_{i}' ) )# 创建基础图表fig = go.Figure( data=[ go.Scatter( x=base_data['x'], y=base_data['y'], mode='markers', marker=dict( size=base_data['size'], color=base_data['category'], colorscale='Rainbow', showscale=True, colorbar=dict(title='类别') ), text=base_data['category'], hovertemplate='<b>类别: %{text}</b><br>X: %{x:.2f}<br>Y: %{y:.2f}<extra></extra>' ) ], frames=frames, layout=go.Layout( title='动态散点图动画', xaxis=dict(range=[-4, 4], title='X轴'), yaxis=dict(range=[-4, 4], title='Y轴'), updatemenus=[ dict( type='buttons', showactive=False, buttons=[ dict( label='播放', method='animate', args=[ None, dict( frame=dict(duration=100, redraw=True), fromcurrent=True, mode='immediate' ) ] ), dict( label='暂停', method='animate', args=[ [None], dict( frame=dict(duration=0, redraw=False), mode='immediate' ) ] ) ] ) ], sliders=[ dict( steps=[ dict( method='animate', args=[ [f'frame_{k}'], dict( mode='immediate', frame=dict(duration=100, redraw=True) ) ], label=str(k) ) for k in range(n_frames) ], active=0, currentvalue=dict(prefix='帧: '), pad=dict(t=50) ) ] ))fig.show()# 另一种动画:条形图竞赛countries = ['USA', 'China', 'Japan', 'Germany', 'UK', 'France', 'India', 'Brazil']years = list(range(2000, 2023))# 创建模拟GDP数据gdp_data = pd.DataFrame({ 'year': np.repeat(years, len(countries)), 'country': np.tile(countries, len(years)), 'gdp': np.random.lognormal(10, 0.5, len(years) * len(countries))})# 按年份排序gdp_data = gdp_data.sort_values(['year', 'gdp'], ascending=[True, False])gdp_data['rank'] = gdp_data.groupby('year').cumcount() + 1# 创建动画条形图竞赛fig = px.bar( gdp_data, x='gdp', y='country', animation_frame='year', animation_group='country', orientation='h', color='country', color_discrete_sequence=px.colors.qualitative.Set3, range_x=[0, gdp_data['gdp'].max() * 1.1], title='GDP增长竞赛 (2000-2022)')fig.update_layout( xaxis_title='GDP (万亿美元)', yaxis_title='国家', showlegend=False, height=600)# 更新动画设置fig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = 500fig.layout.updatemenus[0].buttons[0].args[1]['transition']['duration'] = 300fig.show()
六、地图可视化
# 地理空间数据可视化# 需要安装:pip install geopandastry: import geopandas as gpd world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres')) # 创建交互式地图 fig = px.choropleth( world, locations='iso_a3', color='pop_est', hover_name='name', hover_data=['gdp_md_est', 'continent'], color_continuous_scale=px.colors.sequential.Plasma, title='世界人口分布', labels={'pop_est': '人口估计'} ) fig.update_layout( geo=dict( showframe=False, showcoastlines=True, projection_type='natural earth' ), margin=dict(l=0, r=0, t=50, b=0) ) fig.show()except ImportError: print("geopandas未安装,使用内置数据集") # 使用内置数据集 gapminder = px.data.gapminder() fig = px.scatter_geo( gapminder, locations='iso_alpha', color='continent', size='pop', animation_frame='year', hover_name='country', projection='natural earth', title='全球人口变化 (1952-2007)' ) fig.update_layout( margin=dict(l=0, r=0, t=50, b=0), showlegend=True ) fig.show()# 多种地图类型展示fig = make_subplots( rows=2, cols=2, specs=[ [{'type': 'choropleth'}, {'type': 'scattergeo'}], [{'type': 'densitymapbox'}, {'type': 'linegeo'}] ], subplot_titles=('等值区域图', '地理散点图', '密度地图', '地理线图'), vertical_spacing=0.1, horizontal_spacing=0.1)# 使用示例数据countries_data = pd.DataFrame({ 'country': ['USA', 'China', 'India', 'Brazil', 'Russia', 'Japan'], 'code': ['USA', 'CHN', 'IND', 'BRA', 'RUS', 'JPN'], 'value': [100, 90, 80, 70, 60, 50], 'lat': [39.76, 35.86, 20.59, -14.24, 61.52, 36.20], 'lon': [-98.50, 104.19, 78.96, -51.93, 105.32, 138.25]})# 1. 等值区域图fig.add_trace( go.Choropleth( locations=countries_data['code'], z=countries_data['value'], text=countries_data['country'], colorscale='Blues', showscale=True, colorbar=dict(title='数值') ), row=1, col=1)# 2. 地理散点图fig.add_trace( go.Scattergeo( lat=countries_data['lat'], lon=countries_data['lon'], text=countries_data['country'], marker=dict( size=countries_data['value'], color=countries_data['value'], colorscale='Viridis', showscale=False ), mode='markers+text', textposition='top center' ), row=1, col=2)# 3. 密度地图(需要Mapbox token)# 生成模拟点数据n_points = 1000density_data = pd.DataFrame({ 'lat': np.random.uniform(30, 40, n_points), 'lon': np.random.uniform(110, 120, n_points), 'value': np.random.randn(n_points)})fig.add_trace( go.Densitymapbox( lat=density_data['lat'], lon=density_data['lon'], z=density_data['value'], radius=10, colorscale='Hot' ), row=2, col=1)# 4. 地理线图routes = pd.DataFrame({ 'start_lat': [39.76, 35.86, 20.59], 'start_lon': [-98.50, 104.19, 78.96], 'end_lat': [35.86, 20.59, 39.76], 'end_lon': [104.19, 78.96, -98.50], 'value': [1, 2, 3]})for i, row in routes.iterrows(): fig.add_trace( go.Scattergeo( lat=[row['start_lat'], row['end_lat']], lon=[row['start_lon'], row['end_lon']], mode='lines', line=dict(width=row['value'], color='red'), showlegend=False ), row=2, col=2 )fig.update_layout( title_text='地理空间可视化示例', height=800, geo=dict( projection_type='equirectangular', showland=True, landcolor='lightgray', showocean=True, oceancolor='lightblue' ), geo2=dict( projection_type='mercator' ), mapbox=dict( style='carto-positron', center=dict(lat=35, lon=115), zoom=4 ), geo3=dict( projection_type='natural earth' ))fig.show()
七、仪表板与子图集成
# 创建综合仪表板# 生成模拟业务数据np.random.seed(42)n_days = 90dates = pd.date_range('2023-01-01', periods=n_days, freq='D')business_data = pd.DataFrame({ 'date': dates, 'revenue': 10000 + np.cumsum(np.random.randn(n_days) * 500), 'customers': np.random.poisson(200, n_days), 'conversion_rate': np.random.beta(10, 90, n_days) * 100, 'avg_order_value': 50 + np.random.randn(n_days) * 10, 'region': np.random.choice(['North', 'South', 'East', 'West'], n_days), 'product_category': np.random.choice(['Electronics', 'Clothing', 'Books', 'Home'], n_days)})# 创建仪表板fig = make_subplots( rows=3, cols-3, specs=[ [{'type': 'scatter', 'colspan': 3}, None, None], [{'type': 'bar'}, {'type': 'pie'}, {'type': 'heatmap'}], [{'type': 'box', 'colspan': 2}, None, {'type': 'indicator'}] ], subplot_titles=( '收入趋势', '地区销售分布', '产品类别占比', '转化率热图', '订单价值分布', '关键指标' ), vertical_spacing=0.1, horizontal_spacing=0.1)# 1. 收入趋势(线图)fig.add_trace( go.Scatter( x=business_data['date'], y=business_data['revenue'], mode='lines+markers', name='收入', line=dict(color='royalblue', width=2), marker=dict(size=4), hovertemplate='<b>%{x}</b><br>收入: $%{y:,.0f}<extra></extra>' ), row=1, col=1)# 添加7天移动平均线business_data['revenue_ma'] = business_data['revenue'].rolling(window=7).mean()fig.add_trace( go.Scatter( x=business_data['date'], y=business_data['revenue_ma'], mode='lines', name='7日移动平均', line=dict(color='firebrick', width=2, dash='dash'), hovertemplate='7日平均: $%{y:,.0f}<extra></extra>' ), row=1, col=1)# 2. 地区销售分布(条形图)region_sales = business_data.groupby('region')['revenue'].sum().reset_index()fig.add_trace( go.Bar( x=region_sales['region'], y=region_sales['revenue'], name='地区收入', marker_color=px.colors.qualitative.Set2, text=region_sales['revenue'].apply(lambda x: f'${x:,.0f}'), textposition='auto' ), row=2, col=1)# 3. 产品类别占比(饼图)category_sales = business_data.groupby('product_category').size().reset_index(name='count')fig.add_trace( go.Pie( labels=category_sales['product_category'], values=category_sales['count'], name='产品类别', hole=0.4, marker_colors=px.colors.qualitative.Pastel, textinfo='label+percent' ), row=2, col=2)# 4. 转化率热图# 创建日期-地区矩阵heatmap_data = business_data.pivot_table( index=business_data['date'].dt.dayofweek, columns='region', values='conversion_rate', aggfunc='mean')heatmap_data.index = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']fig.add_trace( go.Heatmap( z=heatmap_data.values, x=heatmap_data.columns, y=heatmap_data.index, colorscale='RdYlGn', text=heatmap_data.values.round(1), texttemplate='%{text}%', textfont=dict(size=10), colorbar=dict(title='转化率%') ), row=2, col=3)# 5. 订单价值分布(箱线图)fig.add_trace( go.Box( y=business_data['avg_order_value'], name='订单价值', boxpoints='outliers', marker_color='lightseagreen', line_color='darkgreen' ), row=3, col=1)# 6. 关键指标(仪表/指示器)current_revenue = business_data['revenue'].iloc[-1]prev_revenue = business_data['revenue'].iloc[-30]revenue_growth = ((current_revenue - prev_revenue) / prev_revenue) * 100current_customers = business_data['customers'].iloc[-7:].mean()prev_customers = business_data['customers'].iloc[-14:-7].mean()customer_growth = ((current_customers - prev_customers) / prev_customers) * 100fig.add_trace( go.Indicator( mode='number+delta', value=current_revenue, number=dict(prefix='$', valueformat=',.0f'), delta=dict( reference=prev_revenue, valueformat=',.0f', prefix='$', relative=False ), title=dict(text='当前收入'), domain=dict(row=3, col=3) ), row=3, col=3)fig.add_trace( go.Indicator( mode='number+delta', value=revenue_growth, number=dict(suffix='%', valueformat='.1f'), delta=dict(reference=0), title=dict(text='收入增长率'), domain=dict(row=3, col=3) ), row=3, col=3)fig.add_trace( go.Indicator( mode='number+delta', value=customer_growth, number=dict(suffix='%', valueformat='.1f'), delta=dict(reference=0), title=dict(text='客户增长率'), domain=dict(row=3, col=3) ), row=3, col=3)# 更新布局fig.update_layout( title_text='业务分析仪表板', height=900, showlegend=True, legend=dict( orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1 ), hovermode='x unified')# 更新子图布局fig.update_xaxes(title_text='日期', row=1, col=1)fig.update_yaxes(title_text='收入 ($)', row=1, col=1)fig.update_xaxes(title_text='地区', row=2, col=1)fig.update_yaxes(title_text='总收入 ($)', row=2, col=1)fig.update_xaxes(title_text='地区', row=2, col=3)fig.update_yaxes(title_text='星期', row=2, col=3)fig.update_yaxes(title_text='订单价值 ($)', row=3, col=1)fig.show()
八、导出与部署
# 1. 导出为不同格式fig = px.scatter(iris, x='sepal_width', y='sepal_length', color='species')# 导出为HTML(交互式)fig.write_html("plotly_chart.html", include_plotlyjs='cdn')# 导出为静态图片fig.write_image("plotly_chart.png", width=1200, height=800, scale=2)fig.write_image("plotly_chart.pdf", width=1200, height=800)fig.write_image("plotly_chart.svg", width=1200, height=800)# 导出为JSONfig.write_json("plotly_chart.json")print("图表已导出为多种格式")# 2. 嵌入到网页中html_template = """<!DOCTYPE html><html><head> <title>Plotly图表嵌入示例</title> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> <style> body { font-family: Arial, sans-serif; margin: 40px; } .chart-container { width: 80%; margin: 0 auto; } </style></head><body> <h1>交互式数据可视化</h1> <div id="chart" class="chart-container"></div> <script> // Plotly图表配置 const data = %s; const layout = %s; Plotly.newPlot('chart', data, layout); </script></body></html>""" % (fig.to_json(), fig.layout.to_json())with open('chart_embed.html', 'w', encoding='utf-8') as f: f.write(html_template)# 3. 使用Dash创建Web应用(预览)dash_code = """import dashfrom dash import dcc, htmlimport plotly.express as pximport pandas as pd# 加载数据df = px.data.iris()# 创建Dash应用app = dash.Dash(__name__)# 定义布局app.layout = html.Div([ html.H1('鸢尾花数据可视化'), html.Div([ html.Label('选择X轴特征:'), dcc.Dropdown( id='x-axis', options=[{'label': col, 'value': col} for col in df.columns[:4]], value='sepal_width' ), ], style={'width': '30%', 'display': 'inline-block'}), html.Div([ html.Label('选择Y轴特征:'), dcc.Dropdown( id='y-axis', options=[{'label': col, 'value': col} for col in df.columns[:4]], value='sepal_length' ), ], style={'width': '30%', 'display': 'inline-block'}), dcc.Graph(id='scatter-plot')])# 定义回调函数@app.callback( dash.dependencies.Output('scatter-plot', 'figure'), [dash.dependencies.Input('x-axis', 'value'), dash.dependencies.Input('y-axis', 'value')])def update_graph(x_col, y_col): fig = px.scatter( df, x=x_col, y=y_col, color='species', title=f'{y_col} vs {x_col}' ) return figif __name__ == '__main__': app.run_server(debug=True)"""print("Dash应用代码已生成,保存为app.py并运行")with open('dash_app.py', 'w', encoding='utf-8') as f: f.write(dash_code)
九、性能优化技巧
# Plotly性能优化import timedef benchmark_plot(): """性能测试函数""" # 测试大数据集 n_points = 100000 big_data = pd.DataFrame({ 'x': np.random.randn(n_points), 'y': np.random.randn(n_points), 'category': np.random.choice(['A', 'B', 'C'], n_points) }) # 方法1:普通散点图(性能较差) start = time.time() fig1 = px.scatter(big_data, x='x', y='y', color='category') time1 = time.time() - start # 方法2:使用WebGL加速 start = time.time() fig2 = go.Figure() fig2.add_trace(go.Scattergl( x=big_data['x'], y=big_data['y'], mode='markers', marker=dict( color=big_data['category'], colorscale='Viridis' ) )) time2 = time.time() - start # 方法3:使用聚合数据 start = time.time() # 对数据进行采样 sampled_data = big_data.sample(n=10000, random_state=42) fig3 = px.scatter(sampled_data, x='x', y='y', color='category') time3 = time.time() - start # 方法4:使用热力图替代 start = time.time() fig4 = go.Figure(go.Histogram2dContour( x=big_data['x'], y=big_data['y'], colorscale='Hot', contours=dict(coloring='heatmap') )) time4 = time.time() - start print(f"性能测试结果 (n={n_points}):") print(f"1. 普通散点图: {time1:.3f}秒") print(f"2. WebGL加速: {time2:.3f}秒") print(f"3. 数据采样: {time3:.3f}秒") print(f"4. 热力图: {time4:.3f}秒") return fig1, fig2, fig3, fig4# 运行性能测试fig1, fig2, fig3, fig4 = benchmark_plot()# 显示性能最好的图表fig4.show()
十、今日练习任务
1. 基础练习
"""任务1:创建交互式股票分析仪表板- 使用yfinance或pandas-datareader获取股票数据- 包含价格走势图、成交量、技术指标- 添加日期范围选择器和指标切换任务2:地理数据可视化项目- 使用全球疫情或经济数据- 创建等值区域图展示数据分布- 添加时间滑块展示变化趋势任务3:3D科学可视化- 创建函数f(x,y) = sin(x)*cos(y)的3D曲面- 添加等高线和切片功能- 实现旋转、缩放交互"""
2. 进阶挑战
"""挑战1:实时数据可视化- 连接实时数据源(API、WebSocket)- 创建实时更新的图表- 实现数据缓冲和性能优化挑战2:机器学习结果可视化- 可视化分类/聚类结果- 展示决策边界和特征重要性- 创建模型性能对比仪表板挑战3:自定义交互功能- 实现图表间的联动- 创建自定义工具栏- 添加数据导出功能"""
3. 实用项目
"""项目:电商用户行为分析平台要求:1. 用户漏斗分析(Plotly漏斗图)2. 用户路径分析(Sankey图)3. 用户分群可视化(雷达图)4. A/B测试结果展示(对比仪表板)5. 响应式设计,支持移动端技术要点:- 使用Plotly创建所有图表- 实现图表间的数据联动- 添加数据筛选和钻取功能- 优化移动端显示效果"""