如何将计数值DataFrame转换为箱线图所需的单个值数据?
问题描述
我有一个存储课程成绩计数值的DataFrame,结构如下:
| Course # | 0 | 4 | 3 | 2 |
|---|---|---|---|---|
| 1801 | 242 | 318 | 222 | 88 |
| 3120 | 75 | 324 | 144 | 30 |
| 1150 | 142 | 295 | 211 | 105 |
| 1811 | 130 | 198 | 265 | 105 |
| 3130 | 45 | 255 | 100 | 24 |
对应的Pandas创建代码:
import pandas as pd df = pd.DataFrame( { 0: {"1801": 242, "3120": 75, "1150": 142, "1811": 130, "3130": 45}, 4: {"1801": 318, "3120": 324, "1150": 295, "1811": 198, "3130": 255}, 3: {"1801": 222, "3120": 144, "1150": 211, "1811": 265, "3130": 100}, 2: {"1801": 88, "3120": 30, "1150": 105, "1811": 105, "3130": 24}, } ) df.index.name = "Course #"
我想用Plotly绘制成绩分布的箱线图,但plotly.express.box需要单个值形式的数据(比如242个0、318个4这类展开后的列表)。目前我通过暴力循环的方式实现了转换,代码如下:
def transform_to_values(s: pd.DataFrame) -> pd.DataFrame: my_dict: dict[str, list[float]] = {} for value in s.itertuples(): my_list: list[float] = [] my_list.extend([4.0] * value._4) my_list.extend([3.0] * value._3) my_list.extend([2.0] * value._2) # 原数据无1分对应的列,注释该行 # my_list.extend([1.0] * value.D) my_list.extend([0.0] * value._0) my_dict[value.Index] = my_list df = pd.DataFrame.from_dict(my_dict, orient="index").T return df
想请教有没有更简便的数据转换方法,或是适配箱线图函数的更优输入方式?
解决方案
方法一:用Pandas内置方法高效转长格式
无需手动循环,借助melt和repeat快速展开数据:
# 转长格式,保留课程号、成绩、计数 melted = df.reset_index().melt(id_vars="Course #", var_name="Score", value_name="Count") # 根据计数值重复行,得到展开后的单个成绩数据 expanded = melted.loc[melted.index.repeat(melted["Count"])].drop("Count", axis=1) # 转换成绩为数值类型 expanded["Score"] = expanded["Score"].astype(float)
直接将expanded传入Plotly Express:
import plotly.express as px fig = px.box(expanded, x="Course #", y="Score") fig.show()
方法二:用Plotly Graph Objects直接处理计数数据(无展开)
如果数据量较大,展开会占用过多内存,可手动计算箱线图统计量后绘制:
import plotly.graph_objects as go import numpy as np fig = go.Figure() for course in df.index: # 获取当前课程的成绩值和对应计数 scores = df.loc[course].index.astype(float) counts = df.loc[course].values # 构造加权成绩数组用于计算统计量 weighted_scores = np.repeat(scores, counts) # 计算箱线图所需统计指标 q1 = np.percentile(weighted_scores, 25) median = np.median(weighted_scores) q3 = np.percentile(weighted_scores, 75) iqr = q3 - q1 min_val = max(np.min(weighted_scores), q1 - 1.5*iqr) max_val = min(np.max(weighted_scores), q3 + 1.5*iqr) # 添加箱线图轨迹 fig.add_trace(go.Box( y=[median, q1, q3, min_val, max_val], name=str(course), boxpoints=False # 不显示原始数据点 )) fig.update_layout(xaxis_title="Course #", yaxis_title="Score") fig.show()
方案对比
- 方法一代码简洁,适配Plotly Express的API,适合数据量较小的场景;
- 方法二避免了数据展开的内存消耗,适合大数据集,手动计算统计量保证性能。
内容的提问来源于Stack Exchange,提问作者Andrew J Bonham
相关产品推荐
相关产品推荐

