Pandas 基础
Pandas 是 Python 数据分析的核心库,提供了高效的数据结构 DataFrame 和 Series,能够方便地处理表格数据。根据 Pandas 官方文档,Pandas 的核心设计理念是数据对齐是内在的(data alignment is intrinsic),这意味着标签与数据之间的联系不会被打断,除非你显式地这样做。
Pandas 数据结构
Pandas 提供两种主要的数据结构:
| 数据结构 | 维度 | 描述 |
|---|---|---|
| Series | 一维 | 带标签的一维数组 |
| DataFrame | 二维 | 带标签的二维表格 |
这两种数据结构都可以看作是数组的容器,实际存储数据并执行计算。理解它们的内部工作原理对于高效使用 Pandas 至关重要。
为什么使用 Pandas?
对比传统 Python 数据处理
# 使用 Python 字典存储表格数据
data = [
{'name': '张三', 'age': 25, 'city': '北京'},
{'name': '李四', 'age': 30, 'city': '上海'},
{'name': '王五', 'age': 35, 'city': '广州'}
]
# 筛选年龄大于28的人 - 需要循环遍历
result = []
for person in data:
if person['age'] > 28:
result.append(person)
# 使用 Pandas
import pandas as pd
df = pd.DataFrame({
'name': ['张三', '李四', '王五'],
'age': [25, 30, 35],
'city': ['北京', '上海', '广州']
})
# 一行代码完成筛选
result = df[df['age'] > 28]
Pandas 的优势远不止简洁:
- 自动数据对齐:操作时会自动按标签对齐数据
- 向量化操作:底层使用 NumPy,避免 Python 循环
- 丰富的数据类型:支持整数、浮点数、字符串、日期时间、分类数据等
- 灵活的索引:支持标签索引、位置索引、布尔索引
- 缺失值处理:内置缺失值表示和处理机制
- 强大的 IO 功能:支持 CSV、Excel、SQL、JSON、Parquet 等多种格式
Series 详解
Series 是带标签的一维数组,可以存储任意数据类型(整数、字符串、浮点数、Python 对象等)。轴标签统称为索引(index)。
创建 Series
import pandas as pd
import numpy as np
# 基本创建方式
s = pd.Series(data, index=index)
# data 可以是多种类型:
# 1. 从列表或 ndarray 创建
s = pd.Series([1, 2, 3, 4, 5])
print(s)
# 0 1
# 1 2
# 2 3
# 3 4
# 4 5
# dtype: int64
# 指定索引
s = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])
print(s)
# a 1
# b 2
# c 3
# d 4
# e 5
# dtype: int64
# 2. 从字典创建(键成为索引)
s = pd.Series({'a': 1, 'b': 2, 'c': 3})
print(s)
# a 1
# b 2
# c 3
# dtype: int64
# 指定索引顺序(不存在的键变为 NaN)
s = pd.Series({'a': 1, 'b': 2, 'c': 3}, index=['b', 'c', 'd', 'a'])
print(s)
# b 2.0
# c 3.0
# d NaN
# a 1.0
# dtype: float64
# 3. 从标量值创建(值会重复以匹配索引长度)
s = pd.Series(5.0, index=['a', 'b', 'c', 'd', 'e'])
print(s)
# a 5.0
# b 5.0
# c 5.0
# d 5.0
# e 5.0
# dtype: float64
Series 的双重属性
Series 同时具有 ndarray 和字典的特性,理解这一点对于熟练使用 Pandas 非常重要。
类 ndarray 特性
s = pd.Series([0.1, 0.2, 0.3, 0.4, 0.5], index=['a', 'b', 'c', 'd', 'e'])
# 像数组一样索引
print(s[0]) # 0.1
print(s[:3]) # 切片
print(s[s > s.median()]) # 布尔索引
print(s[[4, 3, 1]]) # 整数列表索引
# 可以传递给 NumPy 函数
print(np.exp(s))
# 获取底层数组
print(s.array) # 返回 ExtensionArray
print(s.to_numpy()) # 返回 NumPy ndarray
类字典特性
s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
# 通过标签获取和设置值
print(s['a']) # 1
s['a'] = 10 # 修改值
# 检查标签是否存在
print('a' in s) # True
print('x' in s) # False
# 访问不存在的标签会抛出 KeyError
# s['x'] # KeyError
Series 属性
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
print(s.values) # 值数组: array([10, 20, 30, 40])
print(s.index) # 索引对象: Index(['a', 'b', 'c', 'd'], dtype='object')
print(s.dtype) # 数据类型: int64
print(s.shape) # 形状: (4,)
print(s.size) # 元素数量: 4
print(s.name) # 名称: None(可以设置)
s.name = 'my_series'
print(s.name) # 'my_series'
Series 向量化运算
s = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
# 与标量运算
print(s + 1) # 所有元素加1
print(s * 2) # 所有元素乘2
# 与另一个 Series 运算(自动对齐索引)
s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([10, 20, 30], index=['a', 'c', 'd']) # 注意索引不同
# 运算时会自动对齐,不匹配的位置填充 NaN
print(s1 + s2)
# a 11.0
# b NaN
# c 23.0
# d NaN
# dtype: float64
# 使用 fill_value 填充缺失值
print(s1.add(s2, fill_value=0))
# a 11.0
# b 2.0
# c 23.0
# d 30.0
# dtype: float64
DataFrame 详解
DataFrame 是 Pandas 最核心的数据结构,可以看作是:
- 共享相同索引的 Series 集合
- 类似于 Excel 表格或 SQL 表的二维数据结构
- 带标签的二维数组
创建 DataFrame
import pandas as pd
# 方法1:从字典创建(最常用)
df = pd.DataFrame({
'name': ['张三', '李四', '王五', '赵六'],
'age': [25, 30, 35, 28],
'city': ['北京', '上海', '广州', '深圳'],
'salary': [10000, 15000, 20000, 12000]
})
print(df)
# name age city salary
# 0 张三 25 北京 10000
# 1 李四 30 上海 15000
# 2 王五 35 广州 20000
# 3 赵六 28 深圳 12000
# 方法2:从字典列表创建
df = pd.DataFrame([
{'name': '张三', 'age': 25, 'city': '北京'},
{'name': '李四', 'age': 30, 'city': '上海'},
{'name': '王五', 'age': 35, 'city': '广州'}
])
# 方法3:从二维数组创建
arr = np.array([[1, 2, 3], [4, 5, 6]])
df = pd.DataFrame(arr, columns=['A', 'B', 'C'], index=['row1', 'row2'])
# 方法4:从 Series 字典创建
df = pd.DataFrame({
'A': pd.Series([1, 2, 3], index=['a', 'b', 'c']),
'B': pd.Series([4, 5, 6], index=['a', 'b', 'd']) # 索引不同
})
# 自动对齐索引,缺失位置填充 NaN
DataFrame 属性和基本信息
df = pd.DataFrame({
'name': ['张三', '李四', '王五', '赵六'],
'age': [25, 30, 35, 28],
'city': ['北京', '上海', '广州', '深圳'],
'salary': [10000, 15000, 20000, 12000]
})
# 基本属性
print(df.shape) # (4, 4) - 行数和列数
print(df.columns) # 列名: Index(['name', 'age', 'city', 'salary'], dtype='object')
print(df.index) # 行索引: RangeIndex(start=0, stop=4, step=1)
print(df.dtypes) # 每列的数据类型
print(df.values) # 底层 NumPy 数组
print(df.size) # 元素总数: 16
# 查看数据
print(df.head(2)) # 前2行
print(df.tail(2)) # 后2行
# 信息摘要
print(df.info()) # 数据类型、非空值数量、内存使用
print(df.describe()) # 数值列的统计摘要
print(df.memory_usage()) # 每列内存使用量
数据选择详解
正确理解 Pandas 的数据选择机制是高效使用 Pandas 的关键。
选择列
df = pd.DataFrame({
'name': ['张三', '李四', '王五'],
'age': [25, 30, 35],
'city': ['北京', '上海', '广州'],
'salary': [10000, 15000, 20000]
})
# 选择单列 - 返回 Series
name_series = df['name']
print(type(name_series)) # <class 'pandas.core.series.Series'>
# 选择多列 - 返回 DataFrame
subset = df[['name', 'salary']]
print(type(subset)) # <class 'pandas.core.frame.DataFrame'>
# 使用点号访问(仅适用于有效的 Python 标识符)
print(df.name) # 等同于 df['name']
# 注意:如果列名与 DataFrame 方法同名(如 count、sum),点号访问会失效
使用 loc 和 iloc
Pandas 提供两种主要的数据访问器:
| 访问器 | 基于什么选择 | 语法 |
|---|---|---|
loc | 标签 | df.loc[row_label, col_label] |
iloc | 位置(整数) | df.iloc[row_pos, col_pos] |
# 使用标签索引 (loc)
df = pd.DataFrame({
'name': ['张三', '李四', '王五'],
'age': [25, 30, 35]
}, index=['a', 'b', 'c'])
# 选择单行
print(df.loc['a'])
# name 张三
# age 25
# Name: a, dtype: object
# 选择多行
print(df.loc[['a', 'c']])
# 行列同时选择
print(df.loc['a', 'name']) # '张三'
print(df.loc[['a', 'b'], ['name', 'age']])
# 切片(注意:loc 切片是包含终点的)
print(df.loc['a':'c']) # 包含 'c'
# 布尔索引
print(df.loc[df['age'] > 28])
# 使用位置索引 (iloc)
print(df.iloc[0]) # 第1行
print(df.iloc[0:2]) # 前2行(不包含第3行)
print(df.iloc[:, 0]) # 第1列
print(df.iloc[0, 0]) # 第1行第1列
print(df.iloc[[0, 2], [0, 1]]) # 选择特定行列
布尔索引
布尔索引是数据筛选最强大的方式之一:
df = pd.DataFrame({
'name': ['张三', '李四', '王五', '赵六'],
'age': [25, 30, 35, 28],
'department': ['技术', '市场', '技术', '人事'],
'salary': [10000, 15000, 20000, 12000]
})
# 单条件
print(df[df['age'] > 30])
print(df[df['department'] == '技术'])
# 组合条件(必须用 & | ~,不能用 and or not)
print(df[(df['age'] > 28) & (df['salary'] > 12000)])
print(df[(df['department'] == '技术') | (df['department'] == '市场')])
print(df[~(df['department'] == '技术')])
# isin 方法
print(df[df['department'].isin(['技术', '市场'])])
# between 方法
print(df[df['age'].between(25, 30)])
query 方法
对于复杂的条件表达式,query 方法提供了更清晰的语法:
# 等价于 df[df['age'] > 30]
df.query('age > 30')
# 多条件
df.query('age > 28 and salary > 12000')
# 使用变量(需要 @ 前缀)
min_salary = 12000
df.query('salary > @min_salary')
# 列表条件
df.query('department in ["技术", "市场"]')
数据操作
添加和删除列
df = pd.DataFrame({
'name': ['张三', '李四', '王五'],
'age': [25, 30, 35]
})
# 添加新列
df['city'] = ['北京', '上海', '广州'] # 直接赋值
df['salary'] = [10000, 15000, 20000] # 直接赋值
df['bonus'] = df['salary'] * 0.1 # 基于现有列计算
df['age_group'] = df['age'].apply(lambda x: '青年' if x < 30 else '中年') # 使用函数
# 使用 assign 方法(适合方法链)
df = df.assign(
total = df['salary'] + df['bonus'],
level = df['salary'].apply(lambda x: '高' if x > 12000 else '低')
)
# 删除列
df = df.drop('bonus', axis=1) # 返回新 DataFrame
df.drop(columns=['bonus', 'total'], inplace=True) # 原地删除
del df['level'] # Python del 语句
# 选择性保留列
df = df[['name', 'age', 'city']]
添加和删除行
df = pd.DataFrame({
'name': ['张三', '李四'],
'age': [25, 30]
})
# 方法1:使用 loc 添加行
df.loc[2] = ['王五', 35]
# 方法2:使用 concat(推荐)
new_row = pd.DataFrame({'name': ['赵六'], 'age': [28]})
df = pd.concat([df, new_row], ignore_index=True)
# 删除行
df = df.drop(0) # 删除索引为0的行
df = df.drop([0, 1]) # 删除多行
# 根据条件删除
df = df[df['age'] > 28] # 只保留年龄大于28的行
排序
df = pd.DataFrame({
'name': ['张三', '李四', '王五', '赵六'],
'age': [25, 30, 35, 28],
'salary': [10000, 15000, 20000, 12000]
})
# 按值排序
df_sorted = df.sort_values('age') # 升序
df_sorted = df.sort_values('age', ascending=False) # 降序
df_sorted = df.sort_values(['age', 'salary'], ascending=[True, False]) # 多列排序
# 按索引排序
df_sorted = df.sort_index()
# 按列名排序
df_sorted = df.sort_index(axis=1)
去重
df = pd.DataFrame({
'name': ['张三', '李四', '张三', '王五'],
'age': [25, 30, 25, 35]
})
# 检测重复
print(df.duplicated()) # 检测完全重复的行
print(df.duplicated(subset=['name'])) # 检测特定列重复
# 删除重复
df_unique = df.drop_duplicates() # 保留第一个
df_unique = df.drop_duplicates(keep='last') # 保留最后一个
df_unique = df.drop_duplicates(subset=['name']) # 根据特定列去重
数据类型详解
Pandas 的数据类型系统扩展了 NumPy 的类型系统,增加了对缺失值更好的支持。
常用数据类型
| 类型 | 描述 | 示例 |
|---|---|---|
int64 | 整数 | pd.Series([1, 2, 3]) |
float64 | 浮点数 | pd.Series([1.0, 2.5]) |
bool | 布尔值 | pd.Series([True, False]) |
object | Python 对象(通常是字符串) | pd.Series(['a', 'b']) |
datetime64[ns] | 日期时间 | pd.to_datetime(['2024-01-01']) |
category | 分类数据 | pd.Series(['a', 'b'], dtype='category') |
Int64 | 可空整数(Pandas 扩展类型) | pd.Series([1, None], dtype='Int64') |
string | 字符串(Pandas 扩展类型) | pd.Series(['a', None], dtype='string') |
类型转换
df = pd.DataFrame({
'A': ['1', '2', '3'],
'B': ['1.5', '2.5', '3.5'],
'C': ['2024-01-01', '2024-01-02', '2024-01-03']
})
# astype 方法
df['A'] = df['A'].astype(int)
df['B'] = df['B'].astype(float)
# to_numeric(更灵活)
df['A'] = pd.to_numeric(df['A'])
df['A'] = pd.to_numeric(df['A'], errors='coerce') # 无效值转为 NaN
df['A'] = pd.to_numeric(df['A'], downcast='integer') # 自动降级到最小类型
# to_datetime
df['C'] = pd.to_datetime(df['C'])
df['C'] = pd.to_datetime(df['C'], format='%Y-%m-%d')
# 转换为分类类型
df['category_col'] = df['A'].astype('category')
# 使用 convert_dtypes 自动推断最佳类型
df = df.convert_dtypes()
可空数据类型
Pandas 2.0 引入了对缺失值更好的支持:
# 传统整数类型不支持 NaN
s = pd.Series([1, 2, None])
print(s.dtype) # float64(自动转为浮点)
# 使用可空整数类型
s = pd.Series([1, 2, None], dtype='Int64')
print(s.dtype) # Int64
print(s) # 0 1, 1 2, 2 <NA>
# 其他可空类型
s_bool = pd.Series([True, False, None], dtype='boolean')
s_str = pd.Series(['a', 'b', None], dtype='string')
内存优化
对于大型数据集,选择正确的数据类型可以显著减少内存使用。
查看内存使用
df = pd.DataFrame({
'id': range(100000),
'name': ['用户' + str(i) for i in range(100000)],
'score': np.random.randint(0, 100, 100000)
})
# 查看每列内存使用
print(df.memory_usage())
# 查看总内存使用
print(f"总内存: {df.memory_usage().sum() / 1024**2:.2f} MB")
# 深度检查(包括 object 类型的实际内容)
print(df.memory_usage(deep=True))
优化数值类型
# 默认 int64 可能过大
df['id'].dtype # int64(8字节)
# 降级到最小合适的类型
df['id'] = pd.to_numeric(df['id'], downcast='unsigned') # uint32(4字节)
df['score'] = pd.to_numeric(df['score'], downcast='unsigned') # uint8(1字节)
# 浮点数降级
df['float_col'] = pd.to_numeric(df['float_col'], downcast='float') # float32
使用分类类型
对于低基数(少量唯一值)的字符串列,使用 category 类型可以大幅节省内存:
df = pd.DataFrame({
'city': ['北京'] * 300000 + ['上海'] * 300000 + ['广州'] * 400000
})
# object 类型的内存使用
print(f"优化前: {df['city'].memory_usage(deep=True) / 1024**2:.2f} MB")
# 转换为分类类型
df['city'] = df['city'].astype('category')
print(f"优化后: {df['city'].memory_usage(deep=True) / 1024**2:.2f} MB")
# 内存使用可减少 10-20 倍
读取时优化
# 读取时指定数据类型
dtypes = {
'id': 'int32',
'name': 'string',
'category': 'category'
}
df = pd.read_csv('large_file.csv', dtype=dtypes)
# 只读取需要的列
df = pd.read_csv('large_file.csv', usecols=['col1', 'col2'])
# 分块读取
for chunk in pd.read_csv('very_large.csv', chunksize=10000):
process(chunk)
数据对齐机制
数据对齐是 Pandas 的核心特性,理解它对于预测操作结果非常重要。
Series 对齐
s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([10, 20, 30], index=['a', 'c', 'd'])
# 运算时自动按索引对齐
result = s1 + s2
print(result)
# a 11.0 # 两边都有
# b NaN # 只在 s1 中
# c 23.0 # 两边都有
# d NaN # 只在 s2 中
# 使用 fill_value 处理缺失
result = s1.add(s2, fill_value=0)
print(result)
# a 11.0
# b 2.0
# c 23.0
# d 30.0
DataFrame 对齐
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]}, index=['x', 'y'])
df2 = pd.DataFrame({'A': [10, 20], 'C': [30, 40]}, index=['x', 'z'])
# 列和行都会对齐
result = df1 + df2
print(result)
# A B C
# x 11.0 NaN NaN
# y NaN NaN NaN
# z NaN NaN NaN
# 使用 fill_value
result = df1.add(df2, fill_value=0)
链式操作和方法链
链式操作是 Pandas 的一种优雅编程风格,可以让代码更清晰易读。
传统风格 vs 链式风格
# 传统风格(多步赋值)
df = pd.read_csv('data.csv')
df = df[df['age'] > 18]
df = df.dropna()
df['income'] = df['income'].astype(float)
df = df.sort_values('income', ascending=False)
result = df.head(10)
# 链式风格
result = (pd.read_csv('data.csv')
.query('age > 18')
.dropna()
.assign(income=lambda x: x['income'].astype(float))
.sort_values('income', ascending=False)
.head(10))
pipe 方法
对于无法直接链式调用的自定义函数,使用 pipe 方法:
def filter_and_transform(df, min_age, multiplier):
df = df[df['age'] >= min_age].copy()
df['adjusted_income'] = df['income'] * multiplier
return df
# 使用 pipe 保持链式风格
result = (pd.read_csv('data.csv')
.pipe(filter_and_transform, min_age=18, multiplier=1.1)
.sort_values('adjusted_income', ascending=False)
.head(10))
常用统计方法
df = pd.DataFrame({
'A': [1, 2, 3, 4, 5],
'B': [10, 20, 30, 40, 50],
'C': [100, 200, 300, 400, 500]
})
# 基本统计
print(df.sum()) # 每列求和
print(df.mean()) # 每列平均值
print(df.median()) # 每列中位数
print(df.std()) # 每列标准差
print(df.var()) # 每列方差
print(df.min()) # 每列最小值
print(df.max()) # 每列最大值
# 按轴计算
print(df.sum(axis=1)) # 每行求和
print(df.mean(axis=1)) # 每行平均值
# 累计统计
print(df.cumsum()) # 累计求和
print(df.cummax()) # 累计最大值
print(df.cumprod()) # 累计乘积
# 其他统计
print(df.count()) # 非空值计数
print(df.nunique()) # 唯一值数量
print(df.quantile(0.5)) # 分位数
print(df.corr()) # 相关系数矩阵
print(df.cov()) # 协方差矩阵
# 描述性统计
print(df.describe())
print(df.describe(include='all')) # 包括非数值列
字符串操作
Pandas 提供了 .str 访问器用于字符串操作:
df = pd.DataFrame({
'name': [' 张三 ', '李四', '王五'],
'email': ['[email protected]', '[email protected]', '[email protected]']
})
# 去除空白
df['name'] = df['name'].str.strip()
# 大小写转换
df['email_lower'] = df['email'].str.lower()
df['name_title'] = df['name'].str.title()
# 字符串包含
mask = df['email'].str.contains('example')
# 正则表达式
df['domain'] = df['email'].str.extract(r'@([a-zA-Z.]+)')
# 分割
df['email_parts'] = df['email'].str.split('@')
# 替换
df['email'] = df['email'].str.replace('example.com', 'new.com')
# 长度
df['name_len'] = df['name'].str.len()
# 开头/结尾判断
mask = df['email'].str.startswith('ZHANG')
mask = df['email'].str.endswith('.com')
处理缺失值
df = pd.DataFrame({
'A': [1, 2, np.nan, 4, 5],
'B': [10, np.nan, 30, np.nan, 50],
'C': [100, 200, 300, 400, 500]
})
# 检测缺失值
print(df.isnull()) # 布尔 DataFrame
print(df.isnull().sum()) # 每列缺失值数量
print(df.isnull().any()) # 每列是否有缺失值
# 删除缺失值
df_cleaned = df.dropna() # 删除有缺失值的行
df_cleaned = df.dropna(axis=1) # 删除有缺失值的列
df_cleaned = df.dropna(how='all') # 只删除全为 NaN 的行
df_cleaned = df.dropna(subset=['A', 'B']) # 只考虑特定列
df_cleaned = df.dropna(thresh=2) # 至少有 2 个非 NaN 值
# 填充缺失值
df_filled = df.fillna(0) # 用 0 填充
df_filled = df.fillna(df.mean()) # 用均值填充
df_filled = df.fillna({'A': 0, 'B': df['B'].mean()}) # 不同列不同填充值
df_filled = df.fillna(method='ffill') # 前向填充
df_filled = df.fillna(method='bfill') # 后向填充
df_filled = df.interpolate() # 线性插值
# 检查是否为 NaN
print(pd.isna(df))
print(pd.notna(df))
实战示例
示例:员工数据分析
# 创建员工数据
np.random.seed(42)
n = 1000
employees = pd.DataFrame({
'employee_id': range(1, n + 1),
'name': ['员工' + str(i) for i in range(1, n + 1)],
'department': np.random.choice(['技术部', '市场部', '人事部', '财务部'], n),
'position': np.random.choice(['专员', '主管', '经理', '总监'], n),
'age': np.random.randint(22, 60, n),
'salary': np.random.randint(5000, 50000, n),
'years_of_service': np.random.randint(0, 20, n),
'performance_score': np.random.uniform(60, 100, n)
})
# 数据概览
print("数据维度:", employees.shape)
print("\n数据类型:")
print(employees.dtypes)
print("\n基本信息:")
print(employees.describe())
# 数据清洗和特征工程
employees_processed = (employees
.assign(
# 创建年龄段
age_group=lambda x: pd.cut(x['age'],
bins=[0, 30, 40, 50, 100],
labels=['青年', '中年', '壮年', '老年']),
# 创建薪资等级
salary_level=lambda x: pd.qcut(x['salary'],
q=4,
labels=['低', '中', '高', '极高']),
# 计算年薪
annual_salary=lambda x: x['salary'] * 12,
# 绩效评级
performance_grade=lambda x: x['performance_score'].apply(
lambda s: 'A' if s >= 90 else 'B' if s >= 80 else 'C' if s >= 70 else 'D'
)
)
# 转换分类类型以节省内存
.astype({
'department': 'category',
'position': 'category',
'age_group': 'category',
'salary_level': 'category',
'performance_grade': 'category'
})
)
# 内存优化效果
print(f"\n优化前内存: {employees.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
print(f"优化后内存: {employees_processed.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
# 分组分析
print("\n各部门平均薪资:")
print(employees_processed.groupby('department')['salary'].mean().round(2))
print("\n各职位人数和平均绩效:")
print(employees_processed.groupby('position').agg({
'employee_id': 'count',
'performance_score': 'mean'
}).rename(columns={'employee_id': '人数', 'performance_score': '平均绩效'}))
print("\n薪资等级分布:")
print(employees_processed['salary_level'].value_counts())
示例:销售数据分析
# 创建销售数据
np.random.seed(42)
n = 5000
sales = pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=n),
'product': np.random.choice(['产品A', '产品B', '产品C', '产品D'], n),
'region': np.random.choice(['华北', '华东', '华南', '西部'], n),
'salesperson': np.random.choice(['张三', '李四', '王五', '赵六', '钱七'], n),
'quantity': np.random.randint(1, 50, n),
'unit_price': np.random.choice([100, 200, 500, 1000], n)
})
# 计算销售额
sales['total'] = sales['quantity'] * sales['unit_price']
# 时间特征提取
sales['month'] = sales['date'].dt.month
sales['weekday'] = sales['date'].dt.day_name()
sales['is_weekend'] = sales['date'].dt.dayofweek >= 5
# 多维度分析
print("各产品销售总额:")
print(sales.groupby('product')['total'].agg(['sum', 'mean', 'count']))
print("\n各区域销售情况:")
print(sales.groupby('region').agg({
'total': 'sum',
'quantity': 'sum'
}))
print("\n销售员业绩排名:")
salesperson_rank = sales.groupby('salesperson')['total'].sum().sort_values(ascending=False)
print(salesperson_rank)
# 透视表分析
pivot = pd.pivot_table(
sales,
values='total',
index='region',
columns='product',
aggfunc='sum',
fill_value=0
)
print("\n区域-产品销售透视表:")
print(pivot)
小结
本章我们详细学习了 Pandas 的核心概念和使用方法:
- 数据结构:Series 是带标签的一维数组,DataFrame 是二维表格
- 数据选择:
loc(标签)、iloc(位置)、布尔索引、query方法 - 数据操作:添加/删除行列、排序、去重、类型转换
- 数据类型:数值、字符串、日期时间、分类类型、可空类型
- 内存优化:类型降级、分类类型、读取优化
- 数据对齐:Pandas 的核心特性,运算时自动按标签对齐
- 链式操作:使用方法链和
pipe编写清晰的数据处理流程 - 统计方法:基本统计、累计统计、相关分析
- 字符串操作:通过
.str访问器进行向量化字符串操作 - 缺失值处理:检测、删除、填充
练习
- 创建一个包含学生成绩的 DataFrame,计算每个学生的总分、平均分,并按总分排名
- 使用类型优化将一个大型 DataFrame 的内存使用减少 50% 以上
- 使用链式操作完成一个完整的数据清洗流程:过滤、转换、分组聚合
参考资源
下一步
现在你已经掌握了 Pandas 的基础,接下来让我们学习 数据读取与写入,了解如何从各种文件格式导入和导出数据!