github star gitee star atomgit star PyPI Downloads AI 编程 AI 交流群

大家好,我是正在实战各种AI项目的程序员晚枫。

今天学习数据变换的行列操作,这是将原始数据整理成分析所需格式的关键技能。

掌握这些操作,你就能灵活地重塑数据结构。


列操作

选择列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd

df = pd.DataFrame({
'A': [1, 2, 3],
'B': [4, 5, 6],
'C': [7, 8, 9],
'D': [10, 11, 12]
})

# 选择单列(Series)
col_a = df['A']

# 选择多列(DataFrame)
cols_ab = df[['A', 'B']]

# 按类型选择
numeric_cols = df.select_dtypes(include=['number']).columns
print(numeric_cols)

添加列

1
2
3
4
5
6
7
8
9
10
# 直接赋值
df['E'] = [13, 14, 15]

# 基于现有列计算
df['总和'] = df['A'] + df['B'] + df['C']
df['平均值'] = df[['A', 'B', 'C']].mean(axis=1)

# 使用assign(链式操作)
df = df.assign(F=lambda x: x['A'] * 2,
G=lambda x: x['B'] + x['C'])

删除列

1
2
3
4
5
6
7
8
9
10
11
12
# 方式1:del
del df['D']

# 方式2:drop
df = df.drop('D', axis=1)
df = df.drop(['B', 'C'], axis=1)

# 方式3:pop(删除并返回该列)
col_d = df.pop('D')

# 方式4:只保留需要的列
df = df[['A', 'B', '总和']]

重命名列

1
2
3
4
5
6
7
8
# 全部重命名
df.columns = ['列A', '列B', '列C']

# 部分重命名
df.rename(columns={'A': '列A', 'B': '列B'}, inplace=True)

# 统一处理列名(如转小写、去空格)
df.columns = df.columns.str.lower().str.strip()

调整列顺序

1
2
3
4
5
6
# 指定完整顺序
df = df[['C', 'A', 'B', 'D']]

# 把某列移到最前面
cols = ['A'] + [col for col in df.columns if col != 'A']
df = df[cols]

行操作

添加行

1
2
3
4
5
6
7
8
9
10
# 使用loc
df.loc[len(df)] = [4, 5, 6, 7] # 添加到最后

# 使用concat
new_row = pd.DataFrame({'A': [4], 'B': [5], 'C': [6], 'D': [7]})
df = pd.concat([df, new_row], ignore_index=True)

# 追加多个字典
rows = [{'A': 4, 'B': 5, 'C': 6}, {'A': 7, 'B': 8, 'C': 9}]
df = pd.concat([df, pd.DataFrame(rows)], ignore_index=True)

删除行

1
2
3
4
5
6
7
8
9
10
11
12
# 按索引删除
df = df.drop(0) # 删除索引为0的行
df = df.drop([0, 2]) # 删除多行

# 按条件删除
df = df[df['A'] > 1] # 保留A>1的行

# 删除前N行
df = df.iloc[2:] # 删除前2行

# 删除后N行
df = df.iloc[:-2] # 删除最后2行

数据透视表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建示例数据
df = pd.DataFrame({
'日期': ['2024-01', '2024-01', '2024-02', '2024-02'],
'产品': ['A', 'B', 'A', 'B'],
'地区': ['北', '南', '北', '南'],
'销量': [100, 150, 120, 180],
'金额': [1000, 1500, 1200, 1800]
})

# 简单透视表
pivot = df.pivot_table(values='销量',
index='日期',
columns='产品',
aggfunc='sum')

# 多维度透视
pivot = df.pivot_table(values=['销量', '金额'],
index=['日期', '地区'],
columns='产品',
aggfunc={'销量': 'sum', '金额': 'mean'},
fill_value=0,
margins=True) # 添加总计

长宽格式转换

melt:宽转长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 宽格式
wide_df = pd.DataFrame({
'姓名': ['张三', '李四'],
'语文': [85, 90],
'数学': [90, 88],
'英语': [78, 92]
})

# 转为长格式
long_df = wide_df.melt(id_vars=['姓名'],
value_vars=['语文', '数学', '英语'],
var_name='科目',
value_name='分数')

print(long_df)
# 姓名 科目 分数
# 0 张三 语文 85
# 1 李四 语文 90
# ...

pivot:长转宽

1
2
3
4
# 长格式转回宽格式
wide_back = long_df.pivot(index='姓名',
columns='科目',
values='分数').reset_index()

实战:销售报表整理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 原始数据(不规范)
raw_data = pd.DataFrame({
'订单号': ['A001', 'A002', 'A003'],
'客户信息': ['张三|北京', '李四|上海', '王五|广州'],
'产品明细': 'iPhone:2,iPad:1;MacBook:1'
})

# 步骤1:拆分客户信息
raw_data[['客户名', '城市']] = raw_data['客户信息'].str.split('|', expand=True)

# 步骤2:展开产品明细(假设有多个产品用分号分隔)
expanded_rows = []
for idx, row in raw_data.iterrows():
products = row['产品明细'].split(';')
for prod in products:
if ':' in prod:
name, qty = prod.split(':')
expanded_rows.append({
'订单号': row['订单号'],
'客户名': row['客户名'],
'城市': row['城市'],
'产品': name,
'数量': int(qty)
})

clean_df = pd.DataFrame(expanded_rows)
print(clean_df)

性能对比:不同行列操作方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd
import numpy as np

df = pd.DataFrame({
f'col_{i}': np.random.randn(100000) for i in range(20)
})

# 添加列
%timeit df['new'] = df['col_0'] * 2 # 直接赋值,最快
%timeit df.assign(new=lambda x: x['col_0']*2) # assign,可链式

# 删除列
%timeit df.drop(columns=['col_0']) # drop
%timeit del df['col_0'] # del,原地操作

进阶用法

assign链式操作

1
2
3
4
5
6
7
8
9
# 链式添加多列(不修改原数据)
result = (df
.assign(
total=df['price'] * df['quantity'],
discount_rate=df['discount'] / df['price'],
net_amount=lambda x: x['total'] * (1 - x['discount_rate'])
)
.query('net_amount > 0')
)

melt与pivot互转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 宽表 → 长表(melt)
wide = pd.DataFrame({
'city': ['北京', '上海'],
'Q1': [100, 120],
'Q2': [110, 130],
'Q3': [105, 125]
})

long = wide.melt(id_vars='city', var_name='quarter', value_name='sales')
print(long)
# city quarter sales
# 0 北京 Q1 100
# 1 上海 Q1 120
# ...

# 长表 → 宽表(pivot)
wide_again = long.pivot(index='city', columns='quarter', values='sales')

explode展开列表列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 一行包含多个值
df = pd.DataFrame({
'user': ['Alice', 'Bob'],
'items': [['苹果', '香蕉'], ['橙子', '葡萄', '西瓜']]
})

# 展开
df_expanded = df.explode('items')
print(df_expanded)
# user items
# 0 Alice 苹果
# 0 Alice 香蕉
# 1 Bob 橙子
# ...

避坑指南

❌ 坑1:pivot有重复值

1
2
3
4
5
6
7
8
9
# pivot要汔回唯一的行列组合,有重复会报错
df.pivot(index='date', columns='category', values='amount')
# ValueError: Index contains duplicate entries!

# 解决方案1:用pivot_table(自动聚合)
df.pivot_table(index='date', columns='category', values='amount', aggfunc='sum')

# 解决方案2:先去重
df.drop_duplicates(subset=['date', 'category']).pivot(...)

❌ 坑2:列名多层索引

1
2
3
4
5
6
7
# groupby + agg后可能产生多层列名
result = df.groupby('city').agg({'salary': ['mean', 'std', 'count']})
print(result.columns) # MultiIndex

# 展平列名
result.columns = ['_'.join(col).strip() for col in result.columns.values]
# 变成 ['salary_mean', 'salary_std', 'salary_count']

实战案例:电商订单宽窄表转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import pandas as pd
import numpy as np

np.random.seed(42)
# 长格式订单数据
n = 10000
orders = pd.DataFrame({
'order_id': np.random.randint(1, 3000, n),
'product': np.random.choice(['手机', '电脑', '平板', '耳机', '手表'], n),
'quantity': np.random.randint(1, 5, n),
'price': np.random.uniform(100, 8000, n).round(2)
})

# 1. 长表 → 宽表:每个订单的商品数量透视表
wide = orders.pivot_table(
index='order_id',
columns='product',
values='quantity',
aggfunc='sum',
fill_value=0
)
print(f"宽表: {wide.shape}")
print(wide.head())

# 2. 宽表 → 长表
long = wide.reset_index().melt(
id_vars='order_id',
var_name='product',
value_name='quantity'
)
long = long[long['quantity'] > 0] # 过滤0
print(f"\n长表: {long.shape}")

# 3. 添加计算列
orders_enhanced = orders.assign(
total=orders['quantity'] * orders['price'],
price_level=pd.cut(orders['price'], bins=[0, 500, 2000, 5000, 10000],
labels=['低价', '中价', '高价', '奢侈品'])
)
print(f"\n价格等级分布:")
print(orders_enhanced['price_level'].value_counts())

实用变换技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 条件赋值
df['level'] = np.where(df['score'] >= 90, 'A',
np.where(df['score'] >= 80, 'B',
np.where(df['score'] >= 70, 'C', 'D')))

# 2. 用cut自动分箱
df['age_group'] = pd.cut(df['age'], bins=[0, 25, 35, 45, 55, 100],
labels=['25以下', '25-35', '35-45', '45-55', '55+'])

# 3. 用qcut等频分箱
df['income_quartile'] = pd.qcut(df['income'], 4, labels=['Q1', 'Q2', 'Q3', 'Q4'])

# 4. 行转列(unstack)
multi = df.groupby(['city', 'year'])['sales'].sum()
wide = multi.unstack() # year变成列

# 5. 列转行(stack)
long = wide.stack() # year变回行

宽表和长表的选择

1
2
3
4
5
6
7
8
9
10
# 宽表:适合展示和给人看
# city Q1 Q2 Q3 Q4
# 北京 100 120 130 150

# 长表:适合分析和画图
# city quarter sales
# 北京 Q1 100
# 北京 Q2 120

# 分析用长表,展示用宽表,随时互转

下节预告

下一课我们将学习数据合并与连接,掌握如何整合多个数据源。

👉 继续阅读:Pandas数据变换-合并与连接


💬 加入学习交流群

扫码加入Python学习交流群,和数千名同学一起进步:

👉 点击加入交流群

群里不定期分享:

  • 数据分析实战案例
  • Python学习资料
  • 求职面试经验
  • 行业最新动态

推荐:AI Python数据分析实战营

🎁 限时福利:送《利用Python进行数据分析》实体书

👉 点击了解详情


课程导航

上一篇: Pandas数据清洗-类型转换与异常值处理

下一篇: Pandas数据变换-合并与连接


PS:数据变换是数据分析的核心技能。熟练掌握行列操作,能应对80%的数据整理需求。



📚 推荐教材

主教材《Excel+Python 飞速搞定数据分析与处理(图灵出品)》

💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!