|

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. 学习建议

  1. 掌握索引机制:loc/iloc/query 的区别和使用场景
  2. 理解数据对齐:合并和运算时的索引对齐规则
  3. 熟练数据清洗:缺失值、重复值、数据类型转换
  4. 分组聚合:groupby 的各种用法和性能优化
  5. 重塑操作:pivot/melt/stack/unstack 的应用
  6. 性能意识:避免循环,向量化操作,选择合适数据类型

DataFrame 是 Pandas 的核心数据结构,掌握其操作是数据分析的基础。通过实际项目练习不同场景的操作,可以快速提升数据处理能力。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注