如何将Pandas宽格式DataFrame转换为长格式并关联及格线列
Pandas宽表转长表:学生成绩数据格式转换
需求说明
现有一个Pandas DataFrame,存储了学生姓名、2023年1-6月(科学科目包含7月)的数学/科学成绩,以及每位学生每月的及格线。需要将这个宽格式数据转换为包含Name、Year、month、Subject、Marks、Passing_marks的长格式。
原始数据样例
Name 2023_01_Maths 2023_02_Maths 2023_03_Maths 2023_04_Maths 2023_05_Maths 2023_06_Maths 2023_01_Science 2023_02_Science 2023_03_Science 2023_04_Science 2023_05_Science 2023_06_Science 2023_07_Science 2023_01_Passing_marks 2023_02_Passing_marks 2023_03_Passing_marks 2023_04_Passing_marks 2023_05_Passing_marks 2023_06_Passing_marks A 4 3 10 9 9 7 3 0 1 10 1 2 6 4 2 9 8 3 0 B 5 7 4 4 6 1 3 7 8 2 3 4 3 5 1 4 6 5 10 C 3 8 9 1 10 1 0 4 7 5 8 0 10 2 8 8 8 10 6 D 6 4 6 9 3 0 10 1 0 7 5 8 5 4 9 6 4 0 8 E 7 3 5 5 6 10 0 2 10 7 10 3 8 1 1 0 9 10 5 F 8 2 7 6 8 4 4 0 4 9 8 1 6 7 7 4 8 1 6 G 4 6 8 8 6 6 0 2 2 10 8 10 5 9 2 6 5 3 6 H 3 4 0 1 0 6 10 6 7 10 1 5 5 5 5 3 6 4 6 I 2 3 3 9 9 10 10 4 3 9 9 8 4 1 5 1 3 2 4 J 0 2 2 2 3 8 6 6 3 9 10 3 1 9 3 6 1 0 9
期望的长格式样例
Name Year month Subject Marks Passing_marks A 2023 01 Maths 4 4 A 2023 01 Science 3 4 B 2023 01 Maths 5 5 B 2023 01 Science 3 5
你尝试的代码(存在小问题)
# 变量名拼写错误:df_subjects 写成了 df_subject df_subjects = df.set_index(['Name'])[['2023_01_Maths' , '2023_02_Maths' , '2023_03_Maths' , '2023_04_Maths' , '2023_05_Maths' ,'2023_06_Maths' , '2023_01_Science' , '2023_02_Science', '2023_03_Science' ,'2023_04_Science', '2023_05_Science' ,'2023_06_Science' , '2023_07_Science']] df_passing_marks = df.set_index(['Name'])[['2023_01_Passing_marks', '2023_02_Passing_marks', '2023_03_Passing_marks', '2023_04_Passing_marks', '2023_05_Passing_marks', '2023_06_Passing_marks']] # 这里变量名错误:df_subject 应该是 df_subjects df_subject.columns = df_subject.columns.str.split("_", expand=True) df_subject = df_subject.stack([0, 1, 2]).reset_index().rename(columns={0: 'Marks'}) # stack 参数问题,以及重命名列名错误:原列是0,不是'marks' df_passing_marks.columns = df_passing_marks.columns.str.split("_", expand=True) df_passing_marks = df_passing_marks.stack([0, 1, 2]).reset_index().rename(columns={'marks': 'Passing_marks'}) # 合并逻辑可行,但需要修正前面的变量名和列名 df_subject.merge(df_passing_marks[['Name', 'level_1', 'level_2', 'Passing_marks']], on=["Name", 'level_1', 'level_2'])
正确的实现方案
方案1:修正原有代码逻辑
先修正变量名和列名问题,再完成合并:
import pandas as pd # 1. 处理成绩数据转长表 df_subjects = df.set_index(['Name'])[['2023_01_Maths', '2023_02_Maths', '2023_03_Maths', '2023_04_Maths', '2023_05_Maths', '2023_06_Maths', '2023_01_Science', '2023_02_Science', '2023_03_Science', '2023_04_Science', '2023_05_Science', '2023_06_Science', '2023_07_Science']] # 拆分列名为多级索引并命名层级 df_subjects.columns = df_subjects.columns.str.split("_", expand=True) df_subjects.columns.names = ['Year', 'month', 'Subject'] # 堆叠成表并重置索引 df_subjects_long = df_subjects.stack(['Year', 'month', 'Subject']).reset_index(name='Marks') # 2. 处理及格线数据转长表 df_passing = df.set_index(['Name'])[['2023_01_Passing_marks', '2023_02_Passing_marks', '2023_03_Passing_marks', '2023_04_Passing_marks', '2023_05_Passing_marks', '2023_06_Passing_marks']] # 拆分列名并命名层级 df_passing.columns = df_passing.columns.str.split("_", expand=True) df_passing.columns.names = ['Year', 'month', 'Type'] # 堆叠及格线,去除多余的Type列 df_passing_long = df_passing.stack(['Year', 'month']).reset_index(name='Passing_marks') df_passing_long = df_passing_long.drop(columns='Type') # 3. 合并两个长表,7月成绩无对应及格线将显示NaN final_df = df_subjects_long.merge(df_passing_long, on=['Name', 'Year', 'month'], how='left') # 调整列顺序为期望格式 final_df = final_df[['Name', 'Year', 'month', 'Subject', 'Marks', 'Passing_marks']]
方案2:使用pd.wide_to_long更简洁
wide_to_long是Pandas专门处理带前缀宽表转长表的工具,代码更简洁:
import pandas as pd # 处理成绩列转长表 df_melt_subj = pd.wide_to_long(df, stubnames=['Maths', 'Science'], i='Name', j='Year_month', sep='_', suffix=r'\d+') df_melt_subj = df_melt_subj.stack().reset_index(name='Marks') df_melt_subj[['Year', 'month']] = df_melt_subj['Year_month'].str.split('_', expand=True) df_melt_subj = df_melt_subj.rename(columns={'level_2': 'Subject'}).drop(columns='Year_month') # 处理及格线列转长表 df_melt_pass = pd.wide_to_long(df, stubnames=['Passing_marks'], i='Name', j='Year_month', sep='_', suffix=r'\d+') df_melt_pass = df_melt_pass.reset_index() df_melt_pass[['Year', 'month']] = df_melt_pass['Year_month'].str.split('_', expand=True) df_melt_pass = df_melt_pass.drop(columns='Year_month') # 合并两个表 final_df = df_melt_subj.merge(df_melt_pass, on=['Name', 'Year', 'month'], how='left') # 调整列顺序 final_df = final_df[['Name', 'Year', 'month', 'Subject', 'Marks', 'Passing_marks']]
运行上述代码后即可得到期望的长格式数据,其中7月的Science成绩对应的Passing_marks会显示NaN(原始数据无7月及格线)。
内容的提问来源于stack exchange,提问作者ggupta
相关产品推荐
相关产品推荐

