如何在保留前两列不变的情况下堆叠DataFrame的多列(Python Pandas)
如何在保留前两列不变的情况下堆叠DataFrame的多列(Python Pandas)
问题描述
我用的是Python 3.11.5,现在手里有个DataFrame,结构如下:包含id编号、每个id共用的日期范围,还有一堆和每行id对应的数值列。
原始DataFrame:
| id | date | 1 | 2 | 3 |
|---|---|---|---|---|
| 1 | 1/1/00 | 200 | 300 | |
| 1 | 1/2/00 | 100 | 200 | 300 |
| 1 | 1/3/00 | 200 | 300 | |
| 2 | 1/1/00 | 200 | 300 | |
| 2 | 1/2/00 | 100 | 200 | 300 |
| 2 | 1/3/00 | 200 | 300 | |
| 3 | 1/1/00 | 200 | 300 | |
| 3 | 1/2/00 | 100 | 200 | 300 |
| 3 | 1/3/00 | 200 | 300 |
我想把它转换成下面的样子:除了id和date列之外的所有列合并成一列,而且数值要和对应行的id匹配上。
目标DataFrame:
| id | date | value |
|---|---|---|
| 1 | 1/1/00 | |
| 1 | 1/2/00 | 100 |
| 1 | 1/3/00 | |
| 2 | 1/1/00 | 200 |
| 2 | 1/2/00 | 200 |
| 2 | 1/3/00 | 200 |
| 3 | 1/1/00 | 300 |
| 3 | 1/2/00 | 300 |
| 3 | 1/3/00 | 300 |
我试过用下面这行stack()代码处理,但没成功。感觉melt()可能可行,但不知道怎么处理这些带编号的列名(实际我的列数比示例多很多),求大神帮忙!
df = df.set_index(['homeid', 'date']).stack().reset_index()
解决方案
你的需求核心不是简单堆叠列,而是根据每行的id值,提取对应编号列的数值,这里给你两种实用的方法:
方法1:用lookup快速匹配(适合Pandas < 2.0版本)
lookup是专门用来根据行和列的对应关系提取值的工具,简直是为你的场景量身定做的:
import pandas as pd # 假设你的原始数据已经加载到df里了 df['value'] = df.lookup(df.index, df['id'].astype(str)) # 只保留需要的三列 result = df[['id', 'date', 'value']]
简单解释:把id转成字符串后,刚好和你的列名"1"、"2"、"3"对应上,lookup会自动在每一行找到对应id列的数值,直接赋值给新的value列,最后筛选出目标列就行。
方法2:用melt+过滤(全Pandas版本通用)
如果你的Pandas是2.0+版本(lookup已被移除),可以用melt配合筛选来实现:
# 先把宽表转成窄表,保留id、date,其他列转成列名和对应值 melted_df = df.melt(id_vars=['id', 'date'], var_name='col_num', value_name='value') # 只保留列名和当前行id匹配的记录 filtered_df = melted_df[melted_df['col_num'] == melted_df['id'].astype(str)] # 按id和date排序,恢复原始顺序(可选) result = filtered_df.sort_values(by=['id', 'date']).reset_index(drop=True) # 删掉中间列col_num result = result.drop('col_num', axis=1)
解释:先用melt把所有数值列拆成col_num(原列名)和value,然后筛选出col_num等于当前行id的记录,这样就拿到了每个id对应列的数值,最后整理成目标结构。
为啥你的stack()代码没生效?
你之前的代码有两个问题:一是把列名写错了(你的原始列是id不是homeid);二是stack()会把所有数值列都堆叠起来,导致每个(id, date)组合会生成多行记录,而不是只保留对应id列的那一行,所以不符合你的需求。
备注:内容来源于stack exchange,提问作者user25024572
相关产品推荐
相关产品推荐

