多期调查数据宽表转长表:高效实现当期与上期数据转换
高效转换调查数据宽表为长表方案
我有一个名为df的调查数据DataFrame,列名中r/h后的数字代表调查波次/期数,包含age、smoke、income等变量(实际数据最多含100个变量,受访者可达50000人,部分变量以h/s开头)。需要将该宽表转换为长表:每个受访者对应(总期数-1)条记录,每条记录包含respondent_id、t_1_period(上期期数),以及各变量的当期值(t_前缀)和上期值(t_1_前缀)。目前用循环方法处理大数据量时速度极慢,求更高效的转换方案。
示例输入数据
| respondent_id | r1age | r2age | r3age | r4age | r1smoke | r2smoke | r3smoke | r4smoke | r1income | r2income | r3income | r4income |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 16178 | 35 | 38 | 41 | 44 | 1 | 1 | 1 | 1 | 60 | 62 | 68 | 70 |
| 161719 | 65 | 68 | 71 | 74 | 0 | 0 | 0 | 1 | 50 | 52 | 54 | 56 |
| 161720 | 47 | 50 | 53 | 56 | 0 | 1 | 0 | 1 | 80 | 82 | 85 | 87 |
期望输出数据
| respondent_id | t_1_period | t_age | t_1_age | t_smoke | t_1_smoke | t_income | t_1_income |
|---|---|---|---|---|---|---|---|
| 16178 | 1 | 38 | 35 | 1 | 1 | 62 | 60 |
| 16178 | 2 | 41 | 38 | 1 | 1 | 68 | 62 |
| 16178 | 3 | 44 | 41 | 1 | 1 | 70 | 68 |
| 161719 | 1 | 68 | 65 | 0 | 0 | 52 | 50 |
| 161719 | 2 | 71 | 68 | 0 | 0 | 54 | 52 |
| 161719 | 3 | 74 | 71 | 1 | 0 | 56 | 54 |
| 161720 | 1 | 50 | 47 | 1 | 0 | 82 | 80 |
| 161720 | 2 | 53 | 50 | 0 | 1 | 85 | 82 |
| 161720 | 3 | 56 | 53 | 1 | 0 | 87 | 85 |
当前低效代码
df = pd.melt(df, id_vars=["respondent_id"]) variable_names = ["age", "smoke", "income"] new_rows = [] for respondent_id in df["respondent_id"].unique(): df_temp = df[df["respondent_id"] == respondent_id] for i in range(2, 5): new_row = {"respondent_id": respondent_id, "t_1_period": i-1} for var in variable_names: if var not in ["income"]: current_var = f"r{i}{var}" previous_var = f"r{i-1}{var}" new_row[f"t_{var}"] = df_temp[df_temp["variable"] == current_var]["value"].values[0] new_row[f"t_1_{var}"] = df_temp[df_temp["variable"] == previous_var]["value"].values[0] elif var == "income": current_var = f"h{i}{var}" previous_var = f"h{i-1}{var}" new_row[f"t_h{var}"] = df_temp[df_temp["variable"] == current_var]["value"].values[0] new_row[f"t_1_h{var}"] = df_temp[df_temp["variable"] == previous_var]["value"].values[0] new_rows.append(new_row) df_periods = pd.DataFrame(new_rows)
高效转换方案
用pandas的矢量化操作替代循环,全程利用内置函数处理,速度能提升数十倍,还能自动适配所有变量,不用手动维护变量列表。具体做法:
- 拆分列名,提取前缀(r/h/s)、期数和变量名;
- 把宽表转成基础长表,统一存储所有变量的各期数据;
- 按受访者+变量分组,用
shift获取上期数据; - 过滤掉没有上期数据的第一期记录;
- 把长表重新转回宽表,整理成目标字段格式。
代码实现:
import pandas as pd # 解析列名:提取前缀、期数、变量名 def parse_column(col_name): if col_name.startswith(('r', 'h', 's')): prefix = col_name[0] period = int(col_name[1]) var_name = col_name[2:] return prefix, period, var_name return None, None, None # 1. 把宽表转为长表,拆分变量信息 df_melted = df.melt(id_vars='respondent_id', var_name='full_var', value_name='current_value') df_melted[['prefix', 'period', 'var']] = df_melted['full_var'].apply(lambda x: pd.Series(parse_column(x))) # 2. 按受访者和变量分组,获取上期值和上期期数 df_melted['prev_value'] = df_melted.groupby(['respondent_id', 'var'])['current_value'].shift(1) df_melted['prev_period'] = df_melted.groupby(['respondent_id', 'var'])['period'].shift(1) # 3. 去掉没有上期数据的行(即第一期) df_melted = df_melted.dropna(subset=['prev_value']) # 4. 重塑为宽表,整理成目标列名 df_pivoted = df_melted.pivot_table( index=['respondent_id', 'prev_period'], columns=['var'], values=['current_value', 'prev_value'], aggfunc='first' ) # 重命名列,符合t_xxx和t_1_xxx的格式 df_pivoted.columns = [ f"t_{var}" if level == 'current_value' else f"t_1_{var}" for level, var in df_pivoted.columns ] # 重置索引,调整列顺序 df_result = df_pivoted.reset_index().rename(columns={'prev_period': 't_1_period'}) target_order = ['respondent_id', 't_1_period'] + sorted([col for col in df_result.columns if col not in ['respondent_id', 't_1_period']]) df_result = df_result[target_order]
方案优势
- 速度快:矢量化操作完全替代Python循环,处理5万条数据的效率远超原代码;
- 通用性强:自动识别所有变量,新增变量无需修改代码;
- 兼容性好:支持r/h/s等不同前缀的变量,自动处理期数匹配。
内容的提问来源于stack exchange,提问作者caproki
相关产品推荐
相关产品推荐

