📖 Pandas 是 Python 数据分析的核心库,提供了高性能、易用的数据结构和数据分析工具,是每个数据分析师和数据科学家的必备技能。
1. Pandas 简介
1.1 什么是 Pandas
Pandas 是 Python 编写的高性能、易于使用的数据分析库,主要提供:
| 特性 | 说明 |
|---|---|
| 📊 DataFrame | 表格化数据结构,类似于 Excel 或 SQL 表 |
| 📈 Series | 一维标签数组,类似 Excel 列 |
| 🔄 数据对齐 | 自动对齐不同数据源的数据 |
| ⚡ 向量化运算 | 无需循环即可对整列数据进行操作 |
| 🛠️ 数据清洗 | 缺失值处理、重复值删除、数据转换 |
| 📁 IO工具 | 支持 CSV、Excel、JSON、SQL、HTML 等格式 |
1.2 Pandas 在数据处理流程中的位置
┌─────────────────────────────────────────────────────────────────┐
│ 数据处理完整流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 数据采集 ──→ 数据存储 ──→ Pandas清洗 ──→ 分析计算 ──→ 可视化 │
│ │ │ │
│ │ ┌─────────────────────┐ │ │
│ └─────────►│ Pandas DataFrame │◄─────────────┘ │
│ └─────────────────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ 读取数据 数据清洗 数据聚合 │
│ │
└─────────────────────────────────────────────────────────────────┘
1.3 Pandas vs 其他工具对比
| 特性 | Pandas | NumPy | Excel | SQL |
|---|---|---|---|---|
| 数据规模 | 中等(百万级) | 小(适合数值计算) | 小(万级) | 大(亿级) |
| 灵活性 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
| 学习曲线 | 中等 | 陡峭 | 平缓 | 中等 |
| 可视化 | 集成 | 需配合 | 内置 | 需导出 |
| 数据类型 | 混合类型 | 仅数值 | 混合类型 | 表格 |
2. 环境准备
2.1 安装 Pandas
# 使用pip安装
pip install pandas numpy
# 或使用conda(如果可用)
conda install pandas numpy
# 安装完整数据分析环境
pip install pandas numpy matplotlib seaborn openpyxl xlrd
2.2 版本检查
import pandas as pd
import numpy as np
# 检查版本
print(f"Pandas版本: {pd.__version__}")
print(f"NumPy版本: {np.__version__}")
# 查看Pandas默认配置
print("\nPandas配置信息:")
print(pd.show_versions())
预期输出:
Pandas版本: 2.2.0
NumPy版本: 1.26.0
Pandas配置信息:
INSTALLED:
pandas 2.2.0
numpy 1.26.0
...
2.3 Jupyter Notebook 集成
# 在Jupyter中设置显示选项
pd.set_option('display.max_columns', 100) # 最多显示100列
pd.set_option('display.width', 200) # 每行最大宽度
pd.set_option('display.max_colwidth', 50) # 列内容最大宽度
# 科学计数法设置
pd.set_option('display.float_format', lambda x: f'{x:.2f}')
3. 数据结构:Series 与 DataFrame
3.1 Series
Series 是带标签的一维数组,类似于 Excel 中的单列数据。
import pandas as pd
import numpy as np
# 3.1.1 从列表创建
s1 = pd.Series([1, 2, 3, 4, 5])
print("从列表创建:")
print(s1)
print(f"\n值: {s1.values}")
print(f"索引: {s1.index.tolist()}")
# 3.1.2 自定义索引
s2 = pd.Series([95, 87, 92, 88, 96],
index=['语文', '数学', '英语', '物理', '化学'])
print("\n自定义索引:")
print(s2)
# 3.1.3 从字典创建
scores = {'Alice': 92, 'Bob': 85, 'Charlie': 88, 'David': 95}
s3 = pd.Series(scores)
print("\n从字典创建:")
print(s3)
# 3.1.4 访问数据
print(f"\nBob的成绩: {s3['Bob']}")
print(f"前2个: {s3.head(2)}")
print(f"大于90的成绩:\n{s3[s3 > 90]}")
输出结果:
从列表创建:
0 1
1 2
2 3
3 4
4 5
自定义索引:
语文 95
数学 87
英语 92
物理 88
化学 96
从字典创建:
Alice 92
Bob 85
Charlie 88
David 95
Bob的成绩: 85
前2个: Alice 92
Bob 85
大于90的成绩:
Alice 92
David 95
3.2 DataFrame
DataFrame 是带标签的二维数据结构,类似于 Excel 表格或 SQL 表。
# 3.2.1 从字典创建
data = {
'姓名': ['张三', '李四', '王五', '赵六', '钱七'],
'年龄': [25, 30, 28, 35, 27],
'城市': ['北京', '上海', '广州', '深圳', '杭州'],
'薪资': [15000, 22000, 18000, 25000, 16000]
}
df = pd.DataFrame(data)
print("DataFrame示例:")
print(df)
print(f"\n形状: {df.shape} (行数, 列数)")
print(f"列名: {df.columns.tolist()}")
print(f"数据类型:\n{df.dtypes}")
输出结果:
DataFrame示例:
姓名 年龄 城市 薪资
0 张三 25 北京 15000
1 李四 30 上海 22000
2 王五 28 广州 18000
3 赵六 35 深圳 25000
4 钱七 27 杭州 16000
形状: (5, 4) (行数, 列数)
列名: ['姓名', '年龄', '城市', '薪资']
数据类型:
姓名 object
年龄 int64
城市 object
薪资 int64
3.3 数据结构可视化
┌─────────────────────────────────────────────────────────────────┐
│ DataFrame 结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ │ 姓名(Str) │ 年龄(Int) │ 城市(Str) │ 薪资(Int)│
│ ────────┼──────────────┼───────────────┼──────────────┼─────────┤
│ index=0 │ 张三 │ 25 │ 北京 │ 15000 │
│ index=1 │ 李四 │ 30 │ 上海 │ 22000 │
│ index=2 │ 王五 │ 28 │ 广州 │ 18000 │
│ index=3 │ 赵六 │ 35 │ 深圳 │ 25000 │
│ index=4 │ 钱七 │ 27 │ 杭州 │ 16000 │
│ │
│ ┌─────────┐ ┌──────────────────────────────────────┐ │
│ │ 列索引 │ │ 行索引 (Index) │ │
│ └─────────┘ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
4. 数据读取与保存
4.1 读取数据
import pandas as pd
# 4.1.1 读取CSV文件(最常用)
df_csv = pd.read_csv('data.csv')
df_csv = pd.read_csv('data.csv', encoding='utf-8') # 指定编码
df_csv = pd.read_csv('data.csv', sep=';') # 指定分隔符
# 4.1.2 读取Excel文件
df_excel = pd.read_excel('data.xlsx')
df_excel = pd.read_excel('data.xlsx', sheet_name='Sheet1') # 指定工作表
df_excel = pd.read_excel('data.xlsx', sheet_name=None) # 读取所有工作表
# 4.1.3 读取JSON文件
df_json = pd.read_json('data.json')
# 4.1.4 读取HTML表格
tables = pd.read_html('webpage.html') # 返回所有表格列表
df_table = tables[0] # 取第一个表格
# 4.1.5 读取常见参数
df = pd.read_csv(
'data.csv',
encoding='utf-8', # 编码格式
sep=',', # 分隔符
header=0, # 表头行(0表示第一行)
index_col=0, # 设为索引的列
usecols=['A', 'B', 'C'], # 只读取指定列
nrows=1000, # 只读取前1000行
na_values=['N/A', 'NULL'], # 识别为缺失值的字符
dtype={'年龄': int} # 指定列的数据类型
)
4.2 保存数据
# 4.2.1 保存为CSV
df.to_csv('output.csv', index=False) # 不保存索引
df.to_csv('output.csv', index=True) # 保存索引
df.to_csv('output.csv', sep=';') # 指定分隔符
# 4.2.2 保存为Excel
df.to_excel('output.xlsx', sheet_name='数据')
df.to_excel('output.xlsx', index=False)
# 4.2.3 保存为JSON
df.to_json('output.json', orient='records', force_ascii=False)
# 4.2.4 保存为HTML
df.to_html('output.html', classes='table table-striped')
# 4.2.5 保存常见参数
df.to_csv(
'output.csv',
index=False, # 是否保存索引
encoding='utf-8-sig', # 中文Excel兼容性编码
sep=',', # 分隔符
na_rep='NULL', # 缺失值表示
float_format='%.2f' # 浮点数格式
)
4.3 读写对比速查表
| 格式 | 读取 | 保存 | 常用参数 |
|---|---|---|---|
| CSV | read_csv() | to_csv() | sep, encoding, index |
| Excel | read_excel() | to_excel() | sheet_name |
| JSON | read_json() | to_json() | orient |
| HTML | read_html() | to_html() | classes |
| SQL | read_sql() | to_sql() | con, if_exists |
5. 数据探索与查看
5.1 基本信息查看
import pandas as pd
import numpy as np
# 创建示例数据
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九'],
'年龄': [25, 30, 28, 35, 27, 32, 29],
'城市': ['北京', '上海', '广州', '深圳', '杭州', '北京', '上海'],
'部门': ['技术', '销售', '技术', '市场', '销售', '技术', '市场'],
'薪资': [15000, 22000, 18000, 25000, 16000, 17000, 21000],
'绩效': [0.85, 0.92, 0.78, 0.88, 0.95, 0.82, 0.90]
})
# 5.1.1 查看数据概览
print("="*50)
print("1. 基本信息")
print("="*50)
print(f"\n数据形状: {df.shape}")
print(f"行数: {df.shape[0]}, 列数: {df.shape[1]}")
print(f"\n列名: {df.columns.tolist()}")
print(f"\n数据类型:\n{df.dtypes}")
# 5.1.2 查看前/后几行
print("\n" + "="*50)
print("2. 查看前5行")
print("="*50)
print(df.head()) # 默认5行
print("\n查看前3行:")
print(df.head(3))
print("\n" + "="*50)
print("3. 查看后3行")
print("="*50)
print(df.tail(3))
# 5.1.3 查看统计信息
print("\n" + "="*50)
print("4. 数值列统计信息")
print("="*50)
print(df.describe())
# 5.1.4 查看所有信息
print("\n" + "="*50)
print("5. 完整信息报告")
print("="*50)
print(df.info())
输出结果:
==================================================
1. 基本信息
==================================================
数据形状: (7, 6)
行数: 7, 列数: 6
列名: ['姓名', '年龄', '城市', '部门', '薪资', '绩效']
数据类型:
姓名 object
年龄 int64
城市 object
部门 object
薪资 int64
绩效 float64
==================================================
2. 查看前5行
==================================================
姓名 年龄 城市 部门 薪资 绩效
0 张三 25 北京 技术 15000 0.85
1 李四 30 上海 销售 22000 0.92
2 王五 28 广州 技术 18000 0.78
3 赵六 35 深圳 市场 25000 0.88
4 钱七 27 杭州 销售 16000 0.95
...
==================================================
4. 数值列统计信息
==================================================
年龄 薪资 绩效
count 7.000000 7.000000 7.000000
mean 29.428571 19000.000000 0.871429
std 3.408340 3585.708710 0.060186
min 25.000000 15000.000000 0.780000
25% 27.500000 16500.000000 0.835000
50% 29.000000 18000.000000 0.870000
75% 31.000000 21500.000000 0.895000
max 35.000000 25000.000000 0.950000
5.2 统计方法速查表
| 方法 | 说明 | 示例 |
|---|---|---|
count() | 非空值数量 | df['年龄'].count() |
mean() | 平均值 | df['薪资'].mean() |
median() | 中位数 | df['薪资'].median() |
std() | 标准差 | df['薪资'].std() |
min() / max() | 最小/最大值 | df['年龄'].min() |
sum() | 求和 | df['薪资'].sum() |
quantile(q) | 分位数 | df['薪资'].quantile(0.25) |
value_counts() | 值计数 | df['城市'].value_counts() |
6. 数据选择与过滤
6.1 列选择
# 6.1.1 选择单列(返回Series)
ages = df['年龄']
print(f"单列选择: {type(ages)}")
print(ages)
# 6.1.2 选择多列(返回DataFrame)
subset = df[['姓名', '年龄', '薪资']]
print(f"\n多列选择: {type(subset)}")
print(subset)
# 6.1.3 使用点号访问(适用于列名无空格的情况)
cities = df.城市
print(f"\n点号访问: {cities.tolist()}")
6.2 行选择
# 6.2.1 按位置选择 - iloc(索引位置)
print("="*50)
print("iloc 按位置选择")
print("="*50)
print("\n选择第1行:")
print(df.iloc[0])
print("\n选择前3行:")
print(df.iloc[:3])
print("\n选择第2-4行:")
print(df.iloc[1:4])
print("\n选择间隔行 (第0,2,4行):")
print(df.iloc[[0, 2, 4]])
# 6.2.2 按标签选择 - loc(索引名称)
print("\n" + "="*50)
print("loc 按标签选择")
print("="*50)
# 先设置索引为姓名
df_indexed = df.set_index('姓名')
print("\n选择'张三'行:")
print(df_indexed.loc['张三'])
print("\n选择'张三'到'王五'行:")
print(df_indexed.loc['张三':'王五'])
print("\n选择多行:")
print(df_indexed.loc[['张三', '李四', '钱七']])
6.3 条件过滤
print("="*50)
print("条件过滤示例")
print("="*50)
# 6.3.1 单条件过滤
print("\n1. 薪资大于20000的员工:")
high_salary = df[df['薪资'] > 20000]
print(high_salary)
# 6.3.2 多条件过滤(使用 & | 括号)
print("\n2. 北京且薪资大于15000:")
condition = (df['城市'] == '北京') & (df['薪资'] > 15000)
print(df[condition])
# 6.3.3 OR条件
print("\n3. 绩效大于0.9 或 薪资大于22000:")
condition = (df['绩效'] > 0.9) | (df['薪资'] > 22000)
print(df[condition])
# 6.3.4 使用isin过滤
print("\n4. 城市在['北京', '上海']:")
print(df[df['城市'].isin(['北京', '上海'])])
# 6.3.5 字符串方法过滤
print("\n5. 姓名包含'三'的员工:")
# 注意:中文需要先编码再匹配,这里演示字符串方法
df_temp = df.copy()
df_temp['姓名_str'] = df_temp['姓名'].astype(str)
print(df_temp[df_temp['姓名_str'].str.contains('三')])
# 6.3.6 组合使用
print("\n6. 综合查询:技术部门 + 绩效前50% + 选择特定列:")
high_performers = df[
(df['部门'] == '技术') &
(df['绩效'] >= df['绩效'].median())
][['姓名', '部门', '薪资', '绩效']]
print(high_performers)
输出结果:
==================================================
条件过滤示例
==================================================
1. 薪资大于20000的员工:
姓名 年龄 城市 部门 薪资 绩效
1 李四 30 上海 销售 22000 0.92
3 赵六 35 深圳 市场 25000 0.88
2. 北京且薪资大于15000:
姓名 年龄 城市 部门 薪资 绩效
0 张三 25 北京 技术 15000 0.85
5 孙八 32 北京 技术 17000 0.82
6.4 数据选择方法对比
┌─────────────────────────────────────────────────────────────────┐
│ 数据选择方法对比 │
├───────────────────┬─────────────────────────────────────────────┤
│ df['列名'] │ 选择单列 → 返回Series │
│ df[['列1','列2']] │ 选择多列 → 返回DataFrame │
│ df.iloc[0] │ 按位置选择行 │
│ df.iloc[0:5] │ 按位置切片选择行 │
│ df.loc['标签'] │ 按标签选择行 │
│ df.loc['a':'c'] │ 按标签切片选择行 │
│ df[条件] │ 按条件过滤行 │
└───────────────────┴─────────────────────────────────────────────┘
7. 数据清洗与处理
7.1 缺失值处理
import pandas as pd
import numpy as np
# 创建含缺失值的数据
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六', '钱七'],
'年龄': [25, 30, np.nan, 35, 27],
'薪资': [15000, 22000, 18000, np.nan, 16000],
'部门': ['技术', '销售', '技术', '市场', None],
'绩效': [0.85, np.nan, 0.78, 0.88, 0.95]
})
print("="*50)
print("原始数据(含缺失值)")
print("="*50)
print(df)
print(f"\n缺失值统计:\n{df.isnull().sum()}")
# 7.1.1 检测缺失值
print("\n" + "="*50)
print("1. 检测缺失值")
print("="*50)
print(f"是否有缺失值: {df.isnull().any().any()}")
print(f"每列缺失值数量:\n{df.isnull().sum()}")
# 7.1.2 删除缺失值
print("\n" + "="*50)
print("2. 删除缺失值")
print("="*50)
print("\n删除任意含缺失值的行:")
print(df.dropna())
print("\n删除所有列都为缺失值的行:")
print(df.dropna(how='all'))
print("\n删除薪资或绩效缺失的行:")
print(df.dropna(subset=['薪资', '绩效']))
# 7.1.3 填充缺失值
print("\n" + "="*50)
print("3. 填充缺失值")
print("="*50)
print("\n用固定值填充年龄:")
print(df['年龄'].fillna(30))
print("\n用均值填充薪资:")
mean_salary = df['薪资'].mean()
print(df['薪资'].fillna(mean_salary))
print("\n用中位数填充薪资:")
median_salary = df['薪资'].median()
print(df['薪资'].fillna(median_salary))
print("\n用前向填充(ffill):")
print(df.fillna(method='ffill'))
print("\n用后向填充(bfill):")
print(df.fillna(method='bfill'))
# 7.1.4 高级填充:按组填充
print("\n" + "="*50)
print("4. 按组填充(部门平均薪资)")
print("="*50)
# 先创建测试数据
df_test = pd.DataFrame({
'姓名': ['A', 'B', 'C', 'D', 'E'],
'部门': ['技术', '技术', '销售', '销售', '技术'],
'薪资': [15000, np.nan, 22000, np.nan, 17000]
})
print(df_test)
print("\n按部门均值填充:")
df_test['薪资'] = df_test.groupby('部门')['薪资'].transform(
lambda x: x.fillna(x.mean())
)
print(df_test)
输出结果:
==================================================
原始数据(含缺失值)
==================================================
姓名 年龄 薪资 部门 绩效
0 张三 25.0 15000.0 技术 0.85
1 李四 30.0 22000.0 销售 NaN
2 王五 NaN 18000.0 技术 0.78
3 赵六 35.0 NaN 市场 0.88
4 钱七 27.0 16000.0 None 0.95
==================================================
1. 检测缺失值
==================================================
是否有缺失值: True
每列缺失值数量:
姓名 0
年龄 1
薪资 1
部门 1
绩效 1
7.2 重复值处理
# 创建含重复值的数据
df = pd.DataFrame({
'姓名': ['张三', '李四', '张三', '王五', '李四', '赵六'],
'年龄': [25, 30, 25, 28, 30, 35],
'城市': ['北京', '上海', '北京', '广州', '上海', '深圳']
})
print("="*50)
print("原始数据(含重复)")
print("="*50)
print(df)
# 7.2.1 检测重复
print("\n检测重复行:")
print(df.duplicated())
print("\n检测姓名重复:")
print(df.duplicated(subset=['姓名']))
# 7.2.2 删除重复
print("\n删除重复行(保留第一个):")
print(df.drop_duplicates())
print("\n删除姓名重复的行(保留最后一个):")
print(df.drop_duplicates(subset=['姓名'], keep='last'))
7.3 数据类型转换
# 7.3.1 查看数据类型
print("="*50)
print("数据类型操作")
print("="*50)
print(f"当前数据类型:\n{df.dtypes}")
# 7.3.2 转换数据类型
df['年龄'] = df['年龄'].astype('float32')
print(f"\n年龄转换为float32: {df['年龄'].dtype}")
# 7.3.3 转换为分类类型(节省内存)
df['部门'] = df['部门'].astype('category')
print(f"\n部门转换为category: {df['部门'].dtype}")
# 7.3.4 字符串转换
df['年龄_str'] = df['年龄'].astype(str)
print(f"\n年龄转字符串:\n{df['年龄_str']}")
# 7.3.5 时间类型转换
df['日期'] = pd.to_datetime(['2024-01-01', '2024-02-15', '2024-03-20',
'2024-04-10', '2024-05-05', '2024-06-18'])
print(f"\n日期类型:\n{df['日期']}")
print(f"日期类型: {df['日期'].dtype}")
7.4 字符串处理
# 创建含字符串的数据
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五'],
'邮箱': ['zhangsan@email.com', 'lisi@EMAIL.COM', 'wangwu@Email.Com'],
'电话': ['138-1234-5678', '139-8765-4321', '137-1111-2222']
})
print("="*50)
print("字符串处理示例")
print("="*50)
# 7.4.1 大小写转换
print("\n邮箱小写化:")
print(df['邮箱'].str.lower())
print("\n邮箱大写化:")
print(df['邮箱'].str.upper())
# 7.4.2 去除空格
df['姓名'] = df['姓名'].str.strip() # 去除首尾空格
df['姓名'] = df['姓名'].str.replace(' ', '') # 去除所有空格
# 7.4.3 字符串替换
print("\n电话号码格式化(去除-):")
print(df['电话'].str.replace('-', ''))
# 7.4.4 字符串分割
print("\n从邮箱提取用户名:")
print(df['邮箱'].str.split('@').str[0])
# 7.4.5 字符串包含判断
print("\n包含'email'的邮箱:")
print(df[df['邮箱'].str.contains('email', case=False)])
8. 数据转换与运算
8.1 列的添加与删除
import pandas as pd
import numpy as np
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六'],
'基本工资': [10000, 15000, 12000, 18000],
'奖金': [2000, 3000, 2500, 4000]
})
print("="*50)
print("列的添加与删除")
print("="*50)
print(df)
# 8.1.1 添加新列
df['总工资'] = df['基本工资'] + df['奖金']
df['税率'] = 0.1
df['税后工资'] = df['总工资'] * (1 - df['税率'])
print("\n添加计算列后:")
print(df)
# 8.1.2 使用assign添加列(链式操作)
df = df.assign(
奖金率=lambda x: x['奖金'] / x['基本工资'],
年薪=lambda x: x['总工资'] * 12
)
print("\n使用assign添加:")
print(df)
# 8.1.3 删除列
df = df.drop(columns=['税率']) # 删除单列
df = df.drop(columns=['总工资', '税后工资']) # 删除多列
print("\n删除列后:")
print(df)
# 8.1.4 重命名列
df = df.rename(columns={
'姓名': 'Name',
'基本工资': 'Base_Salary',
'奖金': 'Bonus'
})
print("\n重命名列后:")
print(df)
8.2 行操作
# 8.2.1 添加行<br>new_row = pd.DataFrame({<br> 'Name': ['孙七'],<br> 'Base_Salary': [14000],<br> 'Bonus': [2800]<br>})<br>df = pd.concat([d# 8.2.1 添加行
new_row = pd.DataFrame({
'Name': ['孙七'],
'Base_Salary': [14000],
'Bonus': [2800]
})
df = pd.concat([df, new_row], ignore_index=True)
print("\n添加新行:")
print(df)
# 8.2.2 删除行
df = df.drop(index=[0]) # 删除第1行
df = df.drop(index=[0, 1]) # 删除多行
print("\n删除第1行后:")
print(df)f, new_row], ignore_index=True)<br>print("\n添加新行:")<br>print(df)<br><br># 8.2.2 删除行<br>df = df.drop(index=[0]) # 删除第1行<br>df = df.drop(index=[0, 1]) # 删除多行<br>print("\n删除第1行后:")<br>print(df)
8.3 排序
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六', '钱七'],
'部门': ['技术', '销售', '技术', '市场', '销售'],
'薪资': [15000, 22000, 18000, 25000, 16000],
'绩效': [0.85, 0.92, 0.78, 0.88, 0.95]
})
print("="*50)
print("排序操作")
print("="*50)
# 8.3.1 按单列排序
print("\n按薪资升序:")
print(df.sort_values('薪资'))
print("\n按薪资降序:")
print(df.sort_values('薪资', ascending=False))
# 8.3.2 按多列排序
print("\n先按部门升序,再按薪资降序:")
print(df.sort_values(['部门', '薪资'], ascending=[True, False]))
# 8.3.3 按索引排序
df_sorted = df.sort_index(ascending=False)
print("\n按索引降序:")
print(df_sorted)
8.4 数学运算
print("="*50)
print("数学运算示例")
print("="*50)
# 8.4.1 基础统计
print(f"\n薪资总和: {df['薪资'].sum()}")
print(f"薪资均值: {df['薪资'].mean():.2f}")
print(f"薪资中位数: {df['薪资'].median()}")
print(f"薪资标准差: {df['薪资'].std():.2f}")
# 8.4.2 累计计算
print("\n累计和:")
print(df['薪资'].cumsum())
print("\n累计最大值:")
print(df['薪资'].cummax())
# 8.4.3 排名
print("\n薪资排名:")
df['薪资排名'] = df['薪资'].rank(ascending=False, method='min')
print(df[['姓名', '薪资', '薪资排名']].sort_values('薪资排名'))
# 8.4.4 百分位排名
print("\n薪资百分位排名:")
df['薪资百分位'] = df['薪资'].rank(pct=True)
print(df[['姓名', '薪资', '薪资百分位']])
8.5 apply 与 lambda
print("="*50)
print("apply 与 lambda 示例")
print("="*50)
# 8.5.1 对单列应用函数
def classify_salary(salary):
if salary < 17000:
return '低'
elif salary < 20000:
return '中'
else:
return '高'
df['薪资等级'] = df['薪资'].apply(classify_salary)
print("\n薪资等级分类:")
print(df[['姓名', '薪资', '薪资等级']])
# 8.5.2 使用lambda
df['绩效评价'] = df['绩效'].apply(
lambda x: '优秀' if x >= 0.9 else ('良好' if x >= 0.8 else '一般')
)
print("\n绩效评价:")
print(df[['姓名', '绩效', '绩效评价']])
# 8.5.3 对整行应用函数
def evaluate(row):
score = row['薪资'] / 10000 + row['绩效'] * 100
if score > 100:
return 'A'
elif score > 80:
return 'B'
else:
return 'C'
df['综合评价'] = df.apply(evaluate, axis=1)
print("\n综合评价:")
print(df[['姓名', '薪资', '绩效', '综合评价']])
9. 分组与聚合
9.1 groupby 基础
import pandas as pd
import numpy as np
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六', '钱七', '孙八'],
'部门': ['技术', '销售', '技术', '市场', '销售', '技术'],
'城市': ['北京', '上海', '广州', '北京', '上海', '广州'],
'薪资': [15000, 22000, 18000, 25000, 16000, 17000],
'绩效': [0.85, 0.92, 0.78, 0.88, 0.95, 0.82]
})
print("="*50)
print("groupby 分组操作")
print("="*50)
# 9.1.1 单列分组
print("\n按部门分组:")
grouped = df.groupby('部门')
print(f"分组对象: {type(grouped)}")
print(f"分组键: {grouped.groups.keys()}")
# 9.1.2 查看每个组
print("\n各组数据:")
for name, group in grouped:
print(f"\n部门: {name}")
print(group[['姓名', '薪资']])
9.2 聚合函数
print("="*50)
print("聚合函数")
print("="*50)
# 9.2.1 单列单聚合
print("\n1. 各部门平均薪资:")
print(df.groupby('部门')['薪资'].mean())
# 9.2.2 多列多聚合
print("\n2. 各部门薪资和绩效统计:")
agg_result = df.groupby('部门').agg({
'薪资': ['sum', 'mean', 'min', 'max'],
'绩效': ['mean', 'count']
})
print(agg_result)
# 9.2.3 自定义聚合名称
print("\n3. 自定义聚合列名:")
result = df.groupby('部门').agg(
总薪资=('薪资', 'sum'),
平均薪资=('薪资', 'mean'),
平均绩效=('绩效', 'mean'),
人数=('姓名', 'count')
)
print(result)
# 9.2.4 常用聚合函数
print("\n4. 常用聚合函数示例:")
print(df.groupby('部门').agg({
'薪资': ['count', 'sum', 'mean', 'std', 'min', 'max']
}))
输出结果:
==================================================
聚合函数
==================================================
1. 各部门平均薪资:
部门
市场 25000.00
技术 16666.67
销售 19000.00
2. 各部门薪资和绩效统计:
薪资 绩效
sum mean min max mean count
部门
市场 25000 25000 25000 25000 0.88 1
技术 50000 16667 15000 18000 0.82 3
销售 38000 19000 16000 22000 0.94 2
9.3 变换与过滤
print("="*50)
print("分组变换与过滤")
print("="*50)
# 9.3.1 transform - 返回与原数据等长的结果
print("\n1. 各部门薪资排名:")
df['部门薪资排名'] = df.groupby('部门')['薪资'].rank(ascending=False)
print(df[['姓名', '部门', '薪资', '部门薪资排名']])
# 9.3.2 计算各部门薪资占部门总额比例
print("\n2. 各部门薪资占比:")
df['薪资占比'] = df.groupby('部门')['薪资'].transform(
lambda x: x / x.sum() * 100
)
print(df[['姓名', '部门', '薪资', '薪资占比']].round(2))
# 9.3.3 计算各部门薪资与平均值的差异
print("\n3. 各员工薪资与部门均值差异:")
df['薪资差异'] = df.groupby('部门')['薪资'].transform(
lambda x: x - x.mean()
)
print(df[['姓名', '部门', '薪资', '薪资差异']].round(2))
# 9.3.4 filter - 按组过滤
print("\n4. 筛选人数大于2的部门:")
filtered = df.groupby('部门').filter(lambda x: len(x) > 2)
print(filtered)
9.4 多级分组
print("="*50)
print("多级分组")
print("="*50)
# 9.4.1 按部门和城市分组
print("\n1. 各部门在各城市的平均薪资:")
result = df.groupby(['部门', '城市']).agg({
'薪资': 'mean',
'绩效': 'mean'
}).round(2)
print(result)
# 9.4.2 转换为宽格式
print("\n2. 透视表形式:")
pivot = df.pivot_table(
values='薪资',
index='城市',
columns='部门',
aggfunc='mean'
).round(0)
print(pivot)
# 9.4.3 分组后迭代
print("\n3. 分组迭代:")
for (dept, city), group in df.groupby(['部门', '城市']):
print(f"\n部门: {dept}, 城市: {city}")
print(f" 人数: {len(group)}, 平均薪资: {group['薪资'].mean():.0f}")
10. 数据合并与连接
10.1 concat 纵向合并
import pandas as pd
df1 = pd.DataFrame({
'姓名': ['张三', '李四'],
'薪资': [15000, 22000]
})
df2 = pd.DataFrame({
'姓名': ['王五', '赵六'],
'薪资': [18000, 25000]
})
df3 = pd.DataFrame({
'姓名': ['钱七'],
'绩效': [0.95]
})
print("="*50)
print("concat 纵向合并")
print("="*50)
# 10.1.1 基本合并
print("\n1. 基础合并:")
result = pd.concat([df1, df2], ignore_index=True)
print(result)
# 10.1.2 处理不同列
print("\n2. 合并不同列的DataFrame:")
result = pd.concat([df1, df3], ignore_index=True)
print(result)
# 10.1.3 只合并相同列
print("\n3. 只合并相同列:")
result = pd.concat([df1, df3], join='inner')
print(result)
10.2 merge 横向连接
employees = pd.DataFrame({
'员工ID': [1, 2, 3, 4],
'姓名': ['张三', '李四', '王五', '赵六'],
'部门ID': [101, 102, 101, 103]
})
departments = pd.DataFrame({
'部门ID': [101, 102, 103],
'部门名': ['技术部', '销售部', '市场部'],
'部门经理': ['张总', '李总', '王总']
})
print("="*50)
print("merge 横向连接")
print("="*50)
# 10.2.1 内连接(默认)
print("\n1. 内连接 (inner):")
result = pd.merge(employees, departments, on='部门ID', how='inner')
print(result)
# 10.2.2 左连接
print("\n2. 左连接 (left):")
result = pd.merge(employees, departments, on='部门ID', how='left')
print(result)
# 10.2.3 右连接
print("\n3. 右连接 (right):")
result = pd.merge(employees, departments, on='部门ID', how='right')
print(result)
# 10.2.4 全外连接
print("\n4. 全外连接 (outer):")
result = pd.merge(employees, departments, on='部门ID', how='outer')
print(result)
连接类型可视化:
┌─────────────────────────────────────────────────────────────────┐
│ merge 连接类型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ employees departments │
│ ┌────┬────┐ ┌────┬────┐ │
│ │ID │部门 │ │ID │经理 │ │
│ ├───┼────┤ ├───┼────┤ │
│ │101 │技术 │ │101 │张总 │ │
│ │102 │销售 │ │102 │李总 │ │
│ │103 │市场 │ │104 │赵总 │ │
│ └────┴────┘ └────┴────┘ │
│ │
│ inner: 保留两表都有ID的 (101, 102) │
│ left: 保留左表全部 (101, 102, 103) │
│ right: 保留右表全部 (101, 102, 104) │
│ outer: 保留两表全部 (101, 102, 103, 104) │
│ │
└─────────────────────────────────────────────────────────────────┘
10.3 join 索引连接
# 10.3.1 使用join连接
df1 = pd.DataFrame({'薪资': [15000, 22000]}, index=['张三', '李四'])
df2 = pd.DataFrame({'绩效': [0.85, 0.92]}, index=['张三', '李四'])
print("\n" + "="*50)
print("join 索引连接")
print("="*50)
print("\n使用join合并:")
result = df1.join(df2)
print(result)
10.4 合并策略对比
| 方法 | 用途 | 合并轴 | 连接键 |
|---|---|---|---|
pd.concat() | 追加行/列 | 纵/横 | 无 |
pd.merge() | 横向连接表 | 横 | 指定列 |
df.join() | 索引连接 | 横 | 索引 |
11. 时间序列处理
11.1 时间类型转换
import pandas as pd
# 11.1.1 创建时间数据
print("="*50)
print("时间序列处理")
print("="*50)
# 从字符串转换
dates = pd.to_datetime(['2024-01-01', '2024-02-15', '2024-03-20'])
print("\n日期数组:")
print(dates)
# 从范围创建
print("\n日期范围:")
date_range = pd.date_range('2024-01-01', periods=10, freq='D')
print(date_range)
# 11.1.2 创建带时间的DataFrame
df = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=100, freq='D'),
'销量': np.random.randint(100, 500, 100)
})
df['日期'] = pd.to_datetime(df['日期'])
print("\n时间数据DataFrame:")
print(df.head(10))
11.2 时间属性提取
print("="*50)
print("时间属性提取")
print("="*50)
df = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=7, freq='D'),
'销量': [120, 150, 130, 180, 200, 170, 160]
})
print("\n时间属性示例:")
print(df)
# 提取年、月、日
df['年'] = df['日期'].dt.year
df['月'] = df['日期'].dt.month
df['日'] = df['日期'].dt.day
df['星期'] = df['日期'].dt.dayofweek # 0=周一, 6=周日
df['星期名称'] = df['日期'].dt.day_name()
df['季度'] = df['日期'].dt.quarter
df['是否周末'] = df['日期'].dt.is_weekend()
print("\n提取时间属性后:")
print(df)
11.3 时间重采样
# 创建月度销售数据
df = pd.DataFrame({
'月份': pd.date_range('2023-01-01', periods=24, freq='ME'),
'销售额': np.random.randint(10000, 50000, 24)
})
print("\n" + "="*50)
print("时间重采样")
print("="*50)
# 设置日期为索引
df.set_index('月份', inplace=True)
# 月转季度
print("\n月度转季度:")
quarterly = df.resample('QE').sum()
print(quarterly)
# 月转年度
print("\n年度汇总:")
yearly = df.resample('YE').agg({
'销售额': ['sum', 'mean', 'max', 'min']
})
print(yearly)
# 移动平均
print("\n3个月移动平均:")
df['移动平均'] = df['销售额'].rolling(window=3).mean()
print(df)
11.4 时间偏移
print("\n" + "="*50)
print("时间偏移操作")
print("="*50)
df = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=5, freq='D'),
'值': [1, 2, 3, 4, 5]
})
print("\n原始数据:")
print(df)
# 向前偏移
print("\n日期偏移(向前3天):")
df['偏移后'] = df['日期'] + pd.Timedelta(days=3)
print(df)
# 计算时间差
df['差值'] = df['偏移后'] - df['日期']
print("\n时间差:")
print(df['差值'])
12. 数据可视化
12.1 Pandas 内置绘图
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 创建示例数据
df = pd.DataFrame({
'月份': ['1月', '2月', '3月', '4月', '5月', '6月'],
'销售额': [12000, 15000, 18000, 16000, 22000, 25000],
'成本': [8000, 9500, 11000, 10000, 13000, 14000],
'利润率': [0.33, 0.37, 0.39, 0.38, 0.41, 0.44]
}, index=['1月', '2月', '3月', '4月', '5月', '6月'])
print("="*50)
print("Pandas 数据可视化")
print("="*50)
# 12.1.1 折线图
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 折线图
df[['销售额', '成本']].plot(kind='line', ax=axes[0, 0], marker='o')
axes[0, 0].set_title('销售额与成本趋势')
axes[0, 0].set_xlabel('月份')
axes[0, 0].set_ylabel('金额')
# 柱状图
df[['销售额', '成本']].plot(kind='bar', ax=axes[0, 1], color=['#2ecc71', '#e74c3c'])
axes[0, 1].set_title('月度销售对比')
axes[0, 1].set_xlabel('月份')
axes[0, 1].tick_params(axis='x', rotation=0)
# 饼图
df['销售额'].plot(kind='pie', ax=axes[1, 0], autopct='%1.1f%%',
startangle=90, explode=[0.05]*6)
axes[1, 0].set_title('各月销售额占比')
axes[1, 0].set_ylabel('')
# 面积图
df['利润率'].plot(kind='area', ax=axes[1, 1], alpha=0.7, color='skyblue')
axes[1, 1].set_title('利润率变化')
axes[1, 1].set_ylabel('利润率')
plt.tight_layout()
plt.savefig('images/pandas_visualization.png', dpi=150, bbox_inches='tight')
plt.show()
print("\n图表已保存至: images/pandas_visualization.png")
可视化效果:
┌─────────────────────────────────────────────────────────────────┐
│ Pandas 数据可视化示例 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 折线图 │ 柱状图 │
│ ┌──────────────────┐ │ ┌──────────────────┐ │
│ │ ● 销售额 │ │ │ ██ │ │
│ │ ○ 成本 │ │ │ ██ ██ │ │
│ └──────────────────┘ │ └──────────────────┘ │
│ │
│ 饼图 │ 面积图 │
│ ┌──────────────────┐ │ ┌──────────────────┐ │
│ │ ● │ │ │ ████████ │ │
│ │ ● ● │ │ │ ████████ │ │
│ └──────────────────┘ │ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
12.2 常见图表类型
| 类型 | 方法 | 适用场景 |
|---|---|---|
| 折线图 | df.plot.line() | 趋势变化 |
| 柱状图 | df.plot.bar() | 类别对比 |
| 饼图 | df.plot.pie() | 占比展示 |
| 散点图 | df.plot.scatter() | 相关性分析 |
| 箱线图 | df.plot.box() | 分布统计 |
| 直方图 | df.plot.hist() | 频率分布 |
| 热力图 | df.corr().plot() | 相关性矩阵 |
12.3 样式设置
# 12.3.1 样式主题
import matplotlib.pyplot as plt
plt.style.use('seaborn-v0_8-darkgrid') # seaborn风格
# 12.3.2 颜色设置
df['销售额'].plot(color='#3498db', linewidth=2, linestyle='--')
# 12.3.3 网格和标签
plt.grid(True, linestyle='--', alpha=0.7)
plt.xlabel('时间', fontsize=12)
plt.ylabel('金额', fontsize=12)
plt.title('销售趋势', fontsize=14, fontweight='bold')
# 12.3.4 图例设置
plt.legend(['销售额', '成本'], loc='upper left', fontsize=10)
# 12.3.5 保存高清图
plt.savefig('output.png', dpi=300, bbox_inches='tight',
facecolor='white', edgecolor='none')
13. 实战项目:电商数据分析
13.1 项目背景
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
print("="*60)
print(" 电商数据分析实战项目")
print("="*60)
# 13.1.1 生成模拟数据
np.random.seed(42)
# 客户数据
customers = pd.DataFrame({
'客户ID': range(1001, 2001),
'姓名': [f'客户{i}' for i in range(1000)],
'年龄': np.random.randint(18, 65, 1000),
'城市': np.random.choice(['北京', '上海', '广州', '深圳', '杭州'], 1000),
'会员等级': np.random.choice(['普通', '银卡', '金卡', '钻石'],
1000, p=[0.5, 0.25, 0.15, 0.1]),
'注册日期': pd.date_range('2023-01-01', periods=1000, freq='6H')
})
# 订单数据
orders = pd.DataFrame({
'订单ID': range(5001, 8001),
'客户ID': np.random.choice(customers['客户ID'], 3000),
'商品类别': np.random.choice(['电子产品', '服装', '食品', '家居', '美妆'], 3000),
'商品名称': np.random.choice(['手机', '电脑', 'T恤', '裙子', '零食',
'沙发', '床品', '口红', '面膜'], 3000),
'单价': np.random.randint(50, 2000, 3000),
'数量': np.random.randint(1, 5, 3000),
'订单日期': pd.date_range('2024-01-01', periods=3000, freq='3H')
})
# 计算订单金额
orders['订单金额'] = orders['单价'] * orders['数量']
# 关联客户数据
df = orders.merge(customers[['客户ID', '姓名', '城市', '会员等级']],
on='客户ID', how='left')
print("\n1. 数据概览")
print("-"*60)
print(f"客户总数: {customers['客户ID'].nunique()}")
print(f"订单总数: {len(orders)}")
print(f"总销售额: ¥{df['订单金额'].sum():,.2f}")
print(f"平均订单金额: ¥{df['订单金额'].mean():.2f}")
13.2 数据清洗
print("\n2. 数据清洗")
print("-"*60)
# 检测缺失值
print(f"缺失值统计:\n{df.isnull().sum()}")
# 删除重复订单
before = len(df)
df = df.drop_duplicates(subset=['订单ID'])
print(f"\n删除重复订单: {before - len(df)}条")
# 过滤异常金额
before = len(df)
df = df[df['订单金额'] > 0]
print(f"过滤异常订单: {before - len(df)}条")
print(f"\n清洗后数据量: {len(df)}条")
13.3 业务分析
print("\n3. 业务指标分析")
print("-"*60)
# 3.1 各品类销售分析
print("\n【各品类销售统计】")
category_stats = df.groupby('商品类别').agg({
'订单ID': 'count',
'订单金额': ['sum', 'mean']
}).round(2)
category_stats.columns = ['订单数', '销售额', '平均订单额']
category_stats = category_stats.sort_values('销售额', ascending=False)
print(category_stats)
# 3.2 各城市分析
print("\n【各城市销售统计】")
city_stats = df.groupby('城市').agg({
'订单ID': 'count',
'订单金额': 'sum',
'客户ID': 'nunique'
}).round(2)
city_stats.columns = ['订单数', '销售额', '客户数']
city_stats['客单价'] = (city_stats['销售额'] / city_stats['客户数']).round(2)
print(city_stats)
# 3.3 会员等级分析
print("\n【会员等级分析】")
vip_stats = df.groupby('会员等级').agg({
'订单ID': 'count',
'订单金额': ['sum', 'mean'],
'客户ID': 'nunique'
}).round(2)
vip_stats.columns = ['订单数', '销售额', '平均订单额', '客户数']
vip_stats['销售占比'] = (vip_stats['销售额'] / vip_stats['销售额'].sum() * 100).round(2)
vip_stats['客单价'] = (vip_stats['销售额'] / vip_stats['客户数']).round(2)
vip_stats = vip_stats.sort_values('销售额', ascending=False)
print(vip_stats)
# 3.4 月度趋势
print("\n【月度销售趋势】")
df['月份'] = df['订单日期'].dt.to_period('M')
monthly_stats = df.groupby('月份').agg({
'订单ID': 'count',
'订单金额': 'sum'
})
monthly_stats['环比增长'] = monthly_stats['订单金额'].pct_change().round(4) * 100
print(monthly_stats)
13.4 数据可视化
print("\n4. 数据可视化")
print("-"*60)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 4.1 品类销售柱状图
ax1 = axes[0, 0]
category_stats['销售额'].plot(kind='bar', ax=ax1, color='#3498db')
ax1.set_title('各品类销售额', fontsize=12, fontweight='bold')
ax1.set_xlabel('商品类别')
ax1.set_ylabel('销售额')
ax1.tick_params(axis='x', rotation=45)
for i, v in enumerate(category_stats['销售额']):
ax1.text(i, v + 50000, f'¥{v/10000:.0f}万', ha='center', fontsize=9)
# 4.2 城市占比饼图
ax2 = axes[0, 1]
city_stats['销售额'].plot(kind='pie', ax=ax2, autopct='%1.1f%%',
startangle=90, explode=[0.02]*5)
ax2.set_title('各城市销售占比', fontsize=12, fontweight='bold')
ax2.set_ylabel('')
# 4.3 月度趋势折线图
ax3 = axes[1, 0]
monthly_stats['订单金额'].plot(kind='line', ax=ax3, marker='o',
linewidth=2, color='#2ecc71')
ax3.fill_between(monthly_stats.index.astype(str), monthly_stats['订单金额'],
alpha=0.3, color='#2ecc71')
ax3.set_title('月度销售趋势', fontsize=12, fontweight='bold')
ax3.set_xlabel('月份')
ax3.set_ylabel('销售额')
# 4.4 会员等级对比
ax4 = axes[1, 1]
vip_stats[['销售额', '客单价']].plot(kind='bar', ax=ax4,
color=['#9b59b6', '#e74c3c'])
ax4.set_title('会员等级销售对比', fontsize=12, fontweight='bold')
ax4.set_xlabel('会员等级')
ax4.set_ylabel('金额')
ax4.tick_params(axis='x', rotation=0)
plt.tight_layout()
plt.savefig('images/ecommerce_analysis.png', dpi=150, bbox_inches='tight')
plt.show()
print("\n分析图表已保存至: images/ecommerce_analysis.png")
13.5 分析结论
print("\n5. 分析结论")
print("="*60)
# 计算关键指标
top_category = category_stats['销售额'].idxmax()
top_city = city_stats['销售额'].idxmax()
top_vip = vip_stats['销售额'].idxmax()
vip_sales_ratio = vip_stats.loc['钻石', '销售额'] / vip_stats['销售额'].sum() * 100
print(f"""
📊 【关键发现】
1. 【品类分析】
- 最畅销品类: {top_category} (销售额最高)
- 最高平均订单额: {category_stats['平均订单额'].idxmax()} (¥{category_stats['平均订单额'].max():.2f})
2. 【地区分析】
- 最大市场: {top_city} (销售额¥{city_stats.loc[top_city, '销售额']:,.0f})
- 最高客单价: {city_stats['客单价'].idxmax()} (¥{city_stats['客单价'].max():.2f})
3. 【会员分析】
- 最高价值会员: {top_vip}
- 钻石会员销售占比: {vip_sales_ratio:.1f}%
4. 【月度趋势】
- 销售高峰月份: {monthly_stats['订单金额'].idxmax()} (¥{monthly_stats['订单金额'].max():,.0f})
- 环比增长最快: {monthly_stats['环比增长'].idxmax()} (+{monthly_stats['环比增长'].max():.1f}%)
💡 【优化建议】
1. 重点发展{top_city}市场,考虑开设线下体验店
2. 针对钻石会员推出专属促销活动,提升复购率
3. 优化{top_category}品类库存,保证供货充足
4. 关注月度趋势,提前规划节假日营销活动
""")
14. 性能优化技巧
14.1 数据类型优化
import pandas as pd
import numpy as np
import time
print("="*50)
print("性能优化技巧")
print("="*50)
# 14.1.1 内存使用分析
df = pd.DataFrame({
'ID': np.arange(1, 100001),
'值': np.random.randint(0, 1000, 100000),
'分数': np.random.random(100000)
})
print(f"\n原始内存使用: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
print(f"数据类型:\n{df.dtypes}")
# 14.1.2 类型优化
df['ID'] = df['ID'].astype('int32')
df['值'] = df['值'].astype('int16')
df['分数'] = df['分数'].astype('float32')
print(f"\n优化后内存: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
print(f"优化后类型:\n{df.dtypes}")
14.2 读取优化
# 14.2.1 使用chunksize分块读取大文件
print("\n分块读取示例:")
start = time.time()
chunks = []
for chunk in pd.read_csv('data.csv', chunksize=10000):
# 对每个chunk进行处理
chunk = chunk[chunk['值'] > 500] # 示例过滤
chunks.append(chunk)
result = pd.concat(chunks, ignore_index=True)
print(f"分块读取耗时: {time.time() - start:.2f}秒")
print(f"结果行数: {len(result)}")
# 14.2.2 只读取需要的列
df = pd.read_csv('data.csv', usecols=['列1', '列2', '列3'])
14.3 操作优化
# 14.3.1 使用内置方法替代apply
print("\n" + "="*50)
print("操作优化对比")
print("="*50)
df = pd.DataFrame({'数值': np.random.randint(0, 1000, 100000)})
# 慢方法:apply + 自定义函数
start = time.time()
df['结果1'] = df['数值'].apply(lambda x: x * 2 if x > 500 else x)
time1 = time.time() - start
print(f"apply方法耗时: {time1:.4f}秒")
# 快方法:向量化操作
start = time.time()
df['结果2'] = df['数值'].where(df['数值'] > 500, df['数值']) * 2
time2 = time.time() - start
print(f"向量化方法耗时: {time2:.4f}秒")
print(f"速度提升: {time1/time2:.1f}倍")
# 14.3.2 使用query替代布尔索引
start = time.time()
result1 = df[df['数值'] > 500]
time1 = time.time() - start
start = time.time()
result2 = df.query('数值 > 500')
time2 = time.time() - start
print(f"\n布尔索引耗时: {time1:.4f}秒")
print(f"query耗时: {time2:.4f}秒")
14.4 优化技巧速查表
| 场景 | 优化方法 | 效果 |
|---|---|---|
| 内存不足 | 降低数据类型精度 | 内存减少50%+ |
| 大文件读取 | 使用chunksize | 避免内存溢出 |
| 循环处理 | 使用向量化 | 速度提升10-100倍 |
| 条件过滤 | 使用query() | 代码更简洁 |
| 字符串操作 | 使用.str.xxx() | 速度更快 |
| 数据合并 | 使用join代替merge | 更快 |
| 重复计算 | 使用groupby.transform | 避免循环 |
15. 常见问题与解决方案
15.1 SettingWithCopyWarning
print("="*50)
print("常见问题与解决方案")
print("="*50)
print("""
【问题1】SettingWithCopyWarning
原因:创建了DataFrame的视图而不是副本,导致修改警告。
❌ 错误示例:
df = pd.DataFrame({'A': [1, 2, 3]})
df2 = df[df['A'] > 1]
df2['B'] = [4, 5] # 警告!
✅ 正确示例:
df = pd.DataFrame({'A': [1, 2, 3]})
df2 = df[df['A'] > 1].copy()
df2['B'] = [4, 5] # 无警告
""")
15.2 类型转换问题
print("""
【问题2】字符串与数值混合导致的类型问题
原因:列中包含字符串和数值混合,pandas自动推断为object类型。
❌ 错误示例:
df['年龄'] = ['25', '30', 'abc', '28']
df['年龄'].mean() # 报错!
✅ 正确示例:
df['年龄'] = pd.to_numeric(df['年龄'], errors='coerce') # 无效值变为NaN
df['年龄'].mean() # 正常工作
""")
15.3 索引混乱
print("""
【问题3】索引混乱问题
原因:删除行后索引不连续,导致数据对齐问题。
❌ 错误示例:
df = pd.DataFrame({'A': [1, 2, 3, 4, 5]})
df2 = df[df['A'] > 2]
# df2索引是[2, 3, 4],不是[0, 1, 2]
✅ 正确示例:
df2 = df[df['A'] > 2].reset_index(drop=True) # 重置索引
# 或者
df = df.reset_index(drop=True) # 重置后再过滤
""")
15.4 时间解析问题
print("""
【问题4】时间解析错误
原因:日期格式不标准或包含时区信息。
❌ 错误示例:
df['日期'] = pd.to_datetime(df['日期']) # 可能失败
✅ 正确示例:
# 指定格式
df['日期'] = pd.to_datetime(df['日期'], format='%Y/%m/%d')
# 自动推断
df['日期'] = pd.to_datetime(df['日期'], infer_datetime_format=True)
# 处理混合格式
df['日期'] = pd.to_datetime(df['日期'], errors='coerce')
""")
15.5 内存不足
print("""
【问题5】内存不足
✅ 解决方案:
1. 降低数据类型精度
df['ID'] = df['ID'].astype('int32')
df['值'] = df['值'].astype('float32')
2. 删除不需要的列
df = df.drop(columns=['列1', '列2'])
3. 分块处理
for chunk in pd.read_csv('file.csv', chunksize=10000):
process(chunk)
4. 使用更高效的数据格式
df.to_parquet('file.parquet') # 比CSV节省空间
5. 只读取需要的列
df = pd.read_csv('file.csv', usecols=['A', 'B', 'C'])
""")
附录
A. 常用命令速查
| 功能 | 命令 | 示例 |
|---|---|---|
| 读取CSV | pd.read_csv() | pd.read_csv('data.csv') |
| 保存CSV | df.to_csv() | df.to_csv('out.csv') |
| 查看前5行 | df.head() | df.head() |
| 查看形状 | df.shape | df.shape |
| 查看列名 | df.columns | df.columns |
| 查看类型 | df.dtypes | df.dtypes |
| 统计描述 | df.describe() | df.describe() |
| 缺失值 | df.isnull() | df.isnull().sum() |
| 去重 | df.drop_duplicates() | df.drop_duplicates() |
| 分组 | df.groupby() | df.groupby('A').sum() |
| 排序 | df.sort_values() | df.sort_values('A') |
| 筛选 | df[df['A']>5] | df[df['A']>5] |
| 合并 | pd.merge() | pd.merge(df1, df2, on='A') |
B. 推荐学习资源
| 资源 | 类型 | 链接 |
|---|---|---|
| Pandas官方文档 | 官方 | https://pandas.pydata.org/docs/ |
| Pandas练习题 | 练习 | https://github.com/guipsamora/pandas_exercises |
| Kaggle Pandas教程 | 互动 | https://www.kaggle.com/learn/pandas |
| 10分钟入门Pandas | 教程 | Pandas官方文档 |
C. 版本信息
import pandas as pd
import numpy as np
print("\n" + "="*50)
print("环境信息")
print("="*50)
print(f"Pandas版本: {pd.__version__}")
print(f"NumPy版本: {np.__version__}")
print(f"Python版本: {__import__('sys').version}")
📝 教程总结
本教程涵盖了 Pandas 的核心知识点:
- 数据结构:Series 和 DataFrame 的创建与操作
- 数据读写:CSV、Excel、JSON 等格式的读取与保存
- 数据探索:数据查看、统计、描述性分析
- 数据选择:列选择、行选择、条件过滤
- 数据清洗:缺失值、重复值、数据类型转换
- 数据转换:列操作、排序、运算
- 分组聚合:groupby、agg、transform、filter
- 数据合并:concat、merge、join
- 时间序列:时间类型、重采样、偏移
- 数据可视化:Pandas 内置绘图
- 性能优化:内存优化、读写优化、操作优化
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END














暂无评论内容