基于列前缀重塑DataFrame:无循环实现列结构转换
如何在Pandas中无循环重塑宽表为指定长表结构
问题描述
我读取了一个CSV文件到DataFrame,结构如下:
| Time | AP1 | AQ1 | AP2 | AQ2 | AP3 | AQ3 | AP4 | AQ4 | AP5 | AQ5 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2000 | 100 | 10 | 300 | 30 | 500 | 50 | 700 | 70 | 900 | 90 |
| 1 | 2001 | 105 | 15 | 305 | 35 | 505 | 55 | 705 | 75 | 905 | 95 |
| 2 | 2002 | 110 | 20 | 310 | 40 | 510 | 60 | 710 | 80 | 910 | 100 |
| 3 | 2003 | 115 | 25 | 315 | 45 | 515 | 65 | 715 | 85 | 915 | 105 |
| 4 | 2004 | 120 | 30 | 320 | 50 | 520 | 70 | 720 | 90 | 920 | 110 |
| 5 | 2005 | 125 | 35 | 325 | 55 | 525 | 75 | 725 | 95 | 925 | 115 |
想要不使用循环将其重塑为仅包含Time、AP、AQ三列的结构,目标结构示例如下:
| Time | AP | AQ | |
|---|---|---|---|
| 0 | 2000 | 100 | 10 |
| 0 | 2000 | 300 | 30 |
| 0 | 2000 | 500 | 50 |
| 0 | 2000 | 700 | 70 |
| 0 | 2000 | 900 | 90 |
| 1 | 2001 | 105 | 15 |
| ... | ... | ... | ... |
解决方案
这是典型的宽表转长表需求,Pandas提供了高效的矢量化方法,完全不需要写循环,推荐两种方案:
方案1:使用pd.wide_to_long(最贴合需求)
这个方法专门为这种带有编号后缀的成对列设计,代码简洁且高效:
import pandas as pd # 实际场景中替换为读取CSV的代码:df = pd.read_csv("your_file.csv") data = { "Time": [2000, 2001, 2002, 2003, 2004, 2005], "AP1": [100, 105, 110, 115, 120, 125], "AQ1": [10, 15, 20, 25, 30, 35], "AP2": [300, 305, 310, 315, 320, 325], "AQ2": [30, 35, 40, 45, 50, 55], "AP3": [500, 505, 510, 515, 520, 525], "AQ3": [50, 55, 60, 65, 70, 75], "AP4": [700, 705, 710, 715, 720, 725], "AQ4": [70, 75, 80, 85, 90, 95], "AP5": [900, 905, 910, 915, 920, 925], "AQ5": [90, 95, 100, 105, 110, 115] } df = pd.DataFrame(data) # 核心重塑代码 result = pd.wide_to_long( df, stubnames=["AP", "AQ"], # 列名的前缀部分 i="Time", # 保留的标识列 j="id", # 后缀编号会生成的临时列(可后续删除) sep="" # 前缀与后缀之间无分隔符,设为空字符串 ).reset_index() # 删除临时的id列,得到目标结构 result = result.drop("id", axis=1) print(result.head(10))
运行后输出的前10行完全符合需求:
Time AP AQ 0 2000 100 10 1 2000 300 30 2 2000 500 50 3 2000 700 70 4 2000 900 90 5 2001 105 15 6 2001 305 35 7 2001 505 55 8 2001 705 75 9 2001 905 95
方案2:使用pd.melt(通用型宽转长方法)
如果你的列名规律更复杂,melt是更通用的选择,步骤稍多但同样高效:
# 拆分AP和AQ列 ap_melt = df.melt(id_vars="Time", value_vars=[col for col in df.columns if col.startswith("AP")], var_name="AP_col", value_name="AP") aq_melt = df.melt(id_vars="Time", value_vars=[col for col in df.columns if col.startswith("AQ")], var_name="AQ_col", value_name="AQ") # 提取列名中的编号,确保AP和AQ能一一对应 ap_melt["id"] = ap_melt["AP_col"].str.extract("(\d+)").astype(int) aq_melt["id"] = aq_melt["AQ_col"].str.extract("(\d+)").astype(int) # 合并两个结果并清理冗余列 result = pd.merge(ap_melt, aq_melt, on=["Time", "id"]).drop(["AP_col", "AQ_col", "id"], axis=1) # 按Time排序,和目标结构对齐(可选) result = result.sort_values("Time").reset_index(drop=True) print(result.head(10))
这个方法同样能得到完全一致的结果,适合列名规则不那么规整的场景。
内容的提问来源于stack exchange,提问作者user8465900
相关产品推荐
相关产品推荐

