Pandas 数据结构 – DataFrame
Pandas DataFrame 数据结构详解
1. DataFrame 概述
DataFrame 是 Pandas 的二维带标签数据结构,类似电子表格或 SQL 表:
- 行索引:标识行(可自定义)
- 列索引:标识列(列名)
- 数据:每个列可以不同类型,但通常保持一致
- 本质:多个 Series 组成的字典,按列组织
import pandas as pd
import numpy as np
# 简单创建
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五'],
'年龄': [25, 30, 28],
'城市': ['北京', '上海', '广州']
})
print(df)
2. 创建 DataFrame
2.1 从字典创建(最常用)
# 字典的 key 成为列名
data = {
'A': [1, 2, 3, 4],
'B': [10, 20, 30, 40],
'C': ['a', 'b', 'c', 'd']
}
df1 = pd.DataFrame(data)
print(df1)
# 指定索引
df2 = pd.DataFrame(data, index=['w', 'x', 'y', 'z'])
print(df2)
2.2 从列表创建
# 列表的列表
data_list = [['张三', 25, '北京'], ['李四', 30, '上海']]
df3 = pd.DataFrame(data_list,
columns=['姓名', '年龄', '城市'],
index=['row1', 'row2'])
print(df3)
# 字典列表
data_dicts = [
{'姓名': '张三', '年龄': 25, '城市': '北京'},
{'姓名': '李四', '年龄': 30, '城市': '上海'}
]
df4 = pd.DataFrame(data_dicts)
print(df4)
2.3 从 NumPy 数组创建
# 随机数据
np_data = np.random.randn(4, 3)
df5 = pd.DataFrame(np_data,
columns=['A', 'B', 'C'],
index=['a', 'b', 'c', 'd'])
print(df5)
2.4 从 Series 创建
s1 = pd.Series([1, 2, 3], name='A')
s2 = pd.Series([4, 5, 6], name='B')
df6 = pd.DataFrame({'A': s1, 'B': s2})
print(df6)
2.5 空 DataFrame 和动态创建
# 空 DataFrame
df_empty = pd.DataFrame(columns=['A', 'B', 'C'])
print(df_empty)
# 动态添加列
df_empty['A'] = [1, 2, 3]
print(df_empty)
3. DataFrame 核心属性
df = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': ['foo', 'bar', 'foo', 'bar'],
'C': [True, False, True, False]
}, index=['w', 'x', 'y', 'z'])
print("=== 核心属性 ===")
print(f"形状: {df.shape}") # (4, 3)
print(f"列名: {df.columns}") # Index(['A', 'B', 'C'])
print(f"索引: {df.index}") # Index(['w', 'x', 'y', 'z'])
print(f"值: {type(df.values)}") # <class 'numpy.ndarray'>
print(f"数据类型:\n{df.dtypes}")
print(f"内存使用: {df.memory_usage(deep=True)}")
print(f"是否有 NaN: {df.isnull().any().any()}")
4. 索引和选择数据
4.1 列选择
# 单列(返回 Series)
print(df['A'])
print(df.A) # 等价于 df['A']
# 多列(返回 DataFrame)
print(df[['A', 'B']])
# 添加新列
df['D'] = df['A'] * 2
print(df['D'])
4.2 行选择
# loc - 标签索引
print(df.loc['w']) # 单行(Series)
print(df.loc[['w', 'y']]) # 多行(DataFrame)
print(df.loc['w':'y']) # 连续行
# iloc - 位置索引
print(df.iloc[0]) # 第一行
print(df.iloc[1:3]) # 第2-3行
print(df.iloc[[0, 2]]) # 第1、3行
4.3 混合索引
# 布尔索引
print(df[df['A'] > 1]) # A列大于1的行
print(df[df['B'] == 'foo']) # B列等于'foo'的行
# 多条件
print(df[(df['A'] > 1) & (df['B'] == 'bar')])
# isin 方法
print(df[df['B'].isin(['foo', 'bar'])])
4.4 Fancy Indexing
# 复杂选择
rows = ['w', 'y']
cols = ['A', 'C']
print(df.loc[rows, cols])
# 位置混合
print(df.iloc[0:2, [0, 2]]) # 前两行,第一和第三列
5. 数据操作
5.1 添加和删除列
# 添加列
df['E'] = range(len(df)) # 序号列
df['F'] = df['A'].apply(lambda x: x**2) # 计算列
# 删除列
df_dropped = df.drop('D', axis=1) # 删除列 D
df_dropped_row = df.drop('w', axis=0) # 删除行 w
# 原地修改
df.drop('E', axis=1, inplace=True)
5.2 行操作
# 添加行
new_row = pd.Series({'A': 5, 'B': 'baz', 'C': True}, name='new')
df_with_new = pd.concat([df, new_row.to_frame().T])
# 或者使用 append(已废弃,推荐 concat)
# df = df.append(new_row, ignore_index=True)
5.3 元素级操作
# 直接赋值
df.loc['w', 'A'] = 100
df.iloc[0, 0] = 200
# 条件赋值
df.loc[df['A'] > 50, 'A'] = 50
# apply 操作
df['B_length'] = df['B'].apply(len)
df['A_squared'] = df['A'].apply(lambda x: x**2 if pd.notna(x) else np.nan)
6. 缺失数据处理
df_with_nan = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, 7, 8],
'C': [np.nan, 10, 11, 12]
})
print("=== 缺失数据处理 ===")
print("原始数据:")
print(df_with_nan)
# 检测
print("\n缺失值统计:")
print(df_with_nan.isnull().sum())
# 填充
df_filled = df_with_nan.fillna(0) # 用0填充
df_mean = df_with_nan.fillna(df_with_nan.mean()) # 用均值填充
df_ffill = df_with_nan.fillna(method='ffill') # 前向填充
df_bfill = df_with_nan.fillna(method='bfill') # 后向填充
# 删除
df_dropped = df_with_nan.dropna() # 删除含NaN的行
df_dropped_cols = df_with_nan.dropna(axis=1) # 删除含NaN的列
# 插值
df_interp = df_with_nan.interpolate(method='linear')
7. 数据排序
print("=== 排序 ===")
df_sort = pd.DataFrame({
'A': [3, 1, 4, 2],
'B': ['c', 'a', 'd', 'b'],
'C': [30, 10, 40, 20]
}, index=['w', 'x', 'y', 'z'])
# 按列排序
print("按A列排序:")
print(df_sort.sort_values('A'))
# 多列排序
print("按A和C列排序:")
print(df_sort.sort_values(['A', 'C']))
# 按索引排序
print("按索引排序:")
print(df_sort.sort_index())
# 按列名排序
print("按列名排序:")
print(df_sort.sort_index(axis=1))
8. 统计和聚合
8.1 描述性统计
print("=== 描述性统计 ===")
df_stats = pd.DataFrame({
'A': [1, 2, 3, 4],
'B': [10, 20, 30, 40],
'C': [1.0, 2.0, 3.0, 4.0]
})
print(df_stats.describe()) # 数值列统计
print(df_stats.describe(include='all')) # 所有列统计
# 各列统计
print("各列统计:")
print(df_stats.agg(['min', 'max', 'mean', 'std']))
8.2 分组操作
df_group = pd.DataFrame({
'类别': ['A', 'A', 'B', 'B', 'A'],
'值1': [1, 2, 3, 4, 5],
'值2': [10, 20, 30, 40, 50]
})
print("=== 分组统计 ===")
grouped = df_group.groupby('类别')
print(grouped.mean())
print(grouped.agg(['sum', 'mean', 'count']))
print(grouped['值1'].agg(['sum', 'mean']))
9. 数据重塑
9.1 转置
print("转置:")
print(df_stats.T)
9.2 pivot 和 melt
# 准备数据
df_pivot = pd.DataFrame({
'日期': ['2023-01', '2023-01', '2023-02', '2023-02'],
'产品': ['A', 'B', 'A', 'B'],
'销量': [100, 150, 200, 180]
})
print("=== pivot ===")
pivot_df = df_pivot.pivot(index='日期', columns='产品', values='销量')
print(pivot_df)
print("\n=== melt (反pivot) ===")
melted = pd.melt(pivot_df.reset_index(), id_vars='日期',
value_vars=['A', 'B'], var_name='产品', value_name='销量')
print(melted)
9.3 堆叠和展开
multi_index = pd.MultiIndex.from_tuples([
('A', '小A'), ('A', '小B'), ('B', '小A'), ('B', '小B')
], names=['大类', '小类'])
df_multi = pd.DataFrame(np.random.randn(4, 2),
index=multi_index,
columns=['值1', '值2'])
print("=== 多级索引 ===")
print(df_multi)
print("\n堆叠:")
print(df_multi.stack())
print("\n展开:")
print(df_multi.stack().unstack())
10. 合并操作
10.1 连接(concat)
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})
print("=== concat ===")
# 行连接
print(pd.concat([df1, df2]))
# 列连接
print(pd.concat([df1, df2], axis=1))
10.2 合并(merge)
left = pd.DataFrame({
'key': ['K0', 'K1', 'K2', 'K3'],
'A': [1, 2, 3, 4],
'B': [5, 6, 7, 8]
})
right = pd.DataFrame({
'key': ['K0', 'K1', 'K2', 'K4'],
'C': [9, 10, 11, 12],
'D': [13, 14, 15, 16]
})
print("=== merge ===")
# 内连接
print(pd.merge(left, right, on='key', how='inner'))
# 左连接
print(pd.merge(left, right, on='key', how='left'))
# 外连接
print(pd.merge(left, right, on='key', how='outer'))
10.3 连接(join)
left_idx = left.set_index('key')
right_idx = right.set_index('key')
print("=== join ===")
print(left_idx.join(right_idx, how='inner'))
11. 输入输出操作
# 创建测试数据
df_io = pd.DataFrame({
'姓名': ['张三', '李四'],
'年龄': [25, 30],
'分数': [85.5, 92.0]
})
### CSV 操作
df_io.to_csv('test.csv', index=False, encoding='utf-8')
df_from_csv = pd.read_csv('test.csv')
### Excel 操作(需要 openpyxl)
try:
df_io.to_excel('test.xlsx', index=False)
df_from_excel = pd.read_excel('test.xlsx')
print("Excel 操作成功")
except ImportError:
print("需要安装 openpyxl: pip install openpyxl")
### JSON 操作
df_io.to_json('test.json', orient='records', force_ascii=False)
df_from_json = pd.read_json('test.json')
### 数据库操作(需要 SQLAlchemy)
# engine = create_engine('sqlite:///test.db')
# df_io.to_sql('table1', engine)
# df_from_sql = pd.read_sql('SELECT * FROM table1', engine)
12. 实际应用示例
12.1 学生成绩分析
students = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六'],
'数学': [85, 92, 78, 95],
'英语': [88, 85, 90, 82],
'专业': ['计算机', '数学', '物理', '计算机']
})
print("=== 学生成绩分析 ===")
# 计算总分和平均分
students['总分'] = students[['数学', '英语']].sum(axis=1)
students['平均分'] = students[['数学', '英语']].mean(axis=1)
# 分组统计
print("按专业统计:")
print(students.groupby('专业').agg({
'数学': 'mean',
'英语': 'mean',
'总分': 'sum'
}))
# 排名
students['数学排名'] = students['数学'].rank(ascending=False)
print("\n排名:")
print(students)
# 筛选优秀学生
excellent = students[students['平均分'] >= 85]
print("\n优秀学生:")
print(excellent)
12.2 销售数据分析
sales = pd.DataFrame({
'日期': pd.date_range('2023-01-01', periods=10, freq='D'),
'产品': ['A', 'B', 'A', 'B', 'A', 'A', 'B', 'A', 'B', 'A'],
'销量': [100, 150, 200, 180, 220, 190, 160, 210, 170, 230],
'单价': [10, 15, 10, 15, 10, 10, 15, 10, 15, 10]
})
sales['收入'] = sales['销量'] * sales['单价']
print("=== 销售分析 ===")
print("每日收入:")
print(sales.groupby('日期')['收入'].sum())
print("\n产品销售统计:")
product_sales = sales.groupby('产品').agg({
'销量': 'sum',
'收入': 'sum',
'单价': 'mean'
})
print(product_sales)
# 按周汇总
sales['周'] = sales['日期'].dt.isocalendar().week
weekly_sales = sales.groupby(['周', '产品'])['收入'].sum().unstack()
print("\n每周产品收入:")
print(weekly_sales)
13. 性能优化技巧
# 1. 选择合适的数据类型
df_optimized = pd.DataFrame({
'category': pd.Categorical(['A', 'B', 'A']), # 分类类型节省内存
'small_int': pd.Series([1, 2, 3], dtype='int8'), # 小整数类型
'float': pd.Series([1.1, 2.2, 3.3], dtype='float32')
})
print("内存优化:")
print(df_optimized.dtypes)
print(f"内存使用: {df_optimized.memory_usage(deep=True).sum()} bytes")
# 2. 向量化操作而非循环
def slow_way(df):
result = []
for val in df['A']:
result.append(val * 2)
return result
def fast_way(df):
return df['A'] * 2
# 3. 使用 query 方法
large_df = pd.DataFrame({'A': range(1000000), 'B': range(1000000)})
# 慢:large_df[(large_df['A'] > 500000) & (large_df['B'] < 500000)]
# 快:large_df.query('A > 500000 and B < 500000')
# 4. 避免链式索引
# 错误:df[df['A'] > 1]['B'] = 10
# 正确:df.loc[df['A'] > 1, 'B'] = 10
14. 常见问题和解决方案
14.1 SettingWithCopyWarning
# 错误写法(会触发警告)
subset = df[df['A'] > 1]
subset['B'] = 10 # SettingWithCopyWarning
# 正确写法
df.loc[df['A'] > 1, 'B'] = 10
# 或
df_copy = df.copy()
df_copy.loc[df_copy['A'] > 1, 'B'] = 10
14.2 内存问题
# 检查内存使用
print(df.info(memory_usage='deep'))
# 优化数据类型
df['category_col'] = df['category_col'].astype('category')
df['int_col'] = pd.to_numeric(df['int_col'], downcast='integer')
# 分块处理大文件
chunk_iter = pd.read_csv('large_file.csv', chunksize=10000)
for chunk in chunk_iter:
# 处理每个块
process(chunk)
14.3 性能瓶颈
# 使用向量化操作
# 慢:for index, row in df.iterrows(): ...
# 快:df.apply(lambda row: ..., axis=1) 或 直接向量化
# 字符串操作使用 vectorized string methods
df['col'].str.lower() # 而不是 apply(lambda x: x.lower())
# 条件选择使用 query 或 vectorized 条件
df.query('A > 1 and B == "foo"') # 通常比布尔索引快
15. 学习建议
- 掌握索引机制:loc/iloc/query 的区别和使用场景
- 理解数据对齐:合并和运算时的索引对齐规则
- 熟练数据清洗:缺失值、重复值、数据类型转换
- 分组聚合:groupby 的各种用法和性能优化
- 重塑操作:pivot/melt/stack/unstack 的应用
- 性能意识:避免循环,向量化操作,选择合适数据类型
DataFrame 是 Pandas 的核心数据结构,掌握其操作是数据分析的基础。通过实际项目练习不同场景的操作,可以快速提升数据处理能力。