Python实现第一层级以颜色和图例区分、仅显示第二层级方块的树形图方案咨询
实现符合需求的多级规整树形图
我来给你几个完美匹配需求的解决方案,都是针对你想要的「第一层级用颜色区分、第二层级方块填满对应父区域」的树形图设计的,完全解决Plotly嵌套、squarify分散的问题:
方案1:用squarify分层绘制(首推,静态图效果拉满)
这个思路很直观:先按第一层级(比如你的city)算出每个组的总销售额,画出大的色块当底色;然后在每个大色块内部,再用该组下的子项(product)的销售额绘制小方块。这样同组的子项会集中在一个规整的区域里,既能直观看到第一层级的占比,又能清晰区分第二层级的细节。
直接上代码:
import pandas as pd import matplotlib.pyplot as plt import squarify import seaborn as sns # 你的测试数据 product = ["Pins", "Binders", "Paper", "Pins", "Binders", "Paper"] city = ["Amsterdam", "Amsterdam", "Amsterdam", "Brussels", "Brussels", "Brussels"] sales = [6, 4, 1, 5, 3, 1] df = pd.DataFrame(dict(product=product, city=city, sales=sales)) # 按城市分组,计算总销售额和子产品的占比 city_groups = df.groupby('city') city_totals = city_groups['sales'].sum().sort_values(ascending=False) # 给每个城市分配专属颜色 color_palette = sns.color_palette("deep", n_colors=len(city_totals)) city_color_map = dict(zip(city_totals.index, color_palette)) fig, ax = plt.subplots(figsize=(12, 8)) ax.set_axis_off() # 第一步:画城市级的大色块(只留底色,不显示城市标签) city_rects = squarify.plot( sizes=city_totals.values, color=[city_color_map[city] for city in city_totals.index], pad=False, # 不留组间空隙,让子产品填满整个城市区块 ax=ax, label=['']*len(city_totals) # 隐藏城市标签 ) # 第二步:在每个城市区块里画产品小方块 for i, (city_name, group_df) in enumerate(city_groups): # 获取当前城市区块的坐标范围 parent_rect = city_rects[i] x, y, dx, dy = parent_rect['x'], parent_rect['y'], parent_rect['dx'], parent_rect['dy'] # 计算产品相对于城市总销售额的大小比例,转换为区块内的尺寸 product_sizes = group_df['sales'].values relative_sizes = [s / city_totals[city_name] * dx * dy for s in product_sizes] # 绘制子方块,颜色用城市色的浅色调,方便区分 squarify.plot( sizes=relative_sizes, label=group_df['product'], color=[sns.desaturate(city_color_map[city_name], 0.8) for _ in product_sizes], pad=True, # 子产品间留小空隙,更易阅读 ax=ax, x=x, y=y, dx=dx, dy=dy ) # 添加城市图例 from matplotlib.patches import Patch legend_elements = [Patch(facecolor=city_color_map[city], label=city) for city in city_totals.index] ax.legend(handles=legend_elements, loc='upper right', bbox_to_anchor=(1.15, 1)) plt.tight_layout() plt.show()
这个方案画出来的图完全符合你的要求:同城市的产品集中在一个大色块里,能一眼看出每个城市的总占比,产品大小清晰,而且是静态图,导出图片毫无问题。
方案2:用Plotly调整参数(适合偶尔需要交互的场景)
如果你偶尔需要交互功能(比如hover看详情),也可以用Plotly的treemap,通过调整参数让第一层级只显示颜色、不显示标签,同时让第二层级填满父区域:
import plotly.express as px import pandas as pd import seaborn as sns # 测试数据 product = ["Pins", "Binders", "Paper", "Pins", "Binders", "Paper"] city = ["Amsterdam", "Amsterdam", "Amsterdam", "Brussels", "Brussels", "Brussels"] sales = [6, 4, 1, 5, 3, 1] df = pd.DataFrame(dict(product=product, city=city, sales=sales)) fig = px.treemap(df, path=['city', 'product'], values='sales', color='city', color_discrete_map=dict(zip(df['city'].unique(), sns.color_palette("deep", 2).as_hex()))) # 配置显示:只显示产品标签,hover时显示城市信息 fig.update_traces( texttemplate='%{label}', textposition='middle center', hovertemplate='Product: %{label}<br>Sales: %{value}<br>City: %{parent}', branchvalues="total" # 确保父节点大小是子节点的总和 ) # 隐藏第一层级的标签(把城市标签设为透明) fig.for_each_trace(lambda t: t.update(textfont=dict(color='rgba(0,0,0,0)') if t.ids[0].count('/') == 0 else {})) fig.update_layout(margin=dict(t=50, l=25, r=25, b=25)) fig.show()
这个方案的效果是:城市作为颜色区分,产品方块填满对应城市的区域,鼠标hover能看到完整信息,适合需要简单交互的场景。
方案3:用专门的treemap库(API最简洁)
还有一个专门的treemap库(先通过pip install treemap安装),它原生支持多级树形图,还能直接控制只显示叶子节点(第二层级)的标签,代码非常简洁:
import pandas as pd import matplotlib.pyplot as plt from treemap import Treemap # 测试数据 product = ["Pins", "Binders", "Paper", "Pins", "Binders", "Paper"] city = ["Amsterdam", "Amsterdam", "Amsterdam", "Brussels", "Brussels", "Brussels"] sales = [6, 4, 1, 5, 3, 1] df = pd.DataFrame(dict(product=product, city=city, sales=sales)) # 创建树形图对象,指定层级、数值和颜色映射 tm = Treemap( data=df, path=['city', 'product'], values='sales', color='city', cmap='deep' ) fig, ax = plt.subplots(figsize=(12, 8)) tm.plot(ax=ax, show_labels='leaf') # 只显示叶子节点(产品)的标签 ax.set_axis_off() plt.legend(handles=tm.get_legend_handles(), loc='upper right', bbox_to_anchor=(1.15, 1)) plt.tight_layout() plt.show()
这个库的API非常友好,不用手动分层绘制,直接就能生成符合你需求的树形图,省了不少代码。
内容的提问来源于stack exchange,提问作者Hippolyte




