当前位置:首页>python>一天一个Python知识点——Day 145:Plotly交互式数据可视化

一天一个Python知识点——Day 145:Plotly交互式数据可视化

  • 2026-08-25 09:31:26
一天一个Python知识点——Day 145:Plotly交互式数据可视化

一、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创建所有图表- 实现图表间的数据联动- 添加数据筛选和钻取功能- 优化移动端显示效果"""

最新文章

随机文章