跳至主要内容
课程笔记约 11 分钟

Python 数据科学:Pandas 与数据处理

整理 Pandas 的 Series、DataFrame、索引、缺失值、分组聚合与常用数据处理方法。

#Python#Pandas#NumPy#数据分析
本文目录
  1. pandas
  2. Pandas 高效数据处理实战指南
  3. 第二章:数据清洗与类型转换
  4. 第三章:多源数据整合
  5. 第四章:高级筛选与过滤
  6. 第五章:聚合分析与数据重塑

pandas

环境配置与导入

在开始之前,需要导入核心库。通常约定俗成的别名如下:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 设置显示选项(可选,用于美观)
pd.options.display.max_rows = 10 # 默认显示10行,超过会折叠

核心数据结构:Series (一维)

Series 就像一个加强版的 Python 列表,或者 Excel 中的一列数据。它由数据索引组成。

2.1 Series 的创建
  • 从列表创建:默认生成整数索引 0, 1, 2...
obj = pd.Series([4, 7, -5, 3])
# 输出:
# 0     4
# 1     7
# 2    -5
# 3     3
# dtype: int64
  • 带标签的索引:指定 index 参数,索引可以是字符串。
obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])
  • 从字典创建:字典的键会自动作为索引。
sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000}
obj3 = pd.Series(sdata)
# 输出:
# Ohio      35000
# Texas     71000
# Oregon    16000
# dtype: int64
2.2 Series 的属性与操作
  • 查看数据和索引

    • obj.values:返回底层的 NumPy 数组。

    • obj.index:返回索引对象。

  • 索引与切片

    • 标签索引obj2['a'] (返回 -5)。

    • 位置索引obj2[2] (返回 -5)。

    • 切片obj2['b':'c'] (包含末端 ‘c’)。

  • 向量化运算

    • 支持 NumPy 的数组运算,如 obj * 2np.exp(obj)
  • 处理缺失值

    • pd.isnull(obj) / obj.isnull():检查是否为 NaN。

    • pd.notnull(obj) / obj.notnull():检查是否非空。


核心数据结构:DataFrame (二维)

DataFrame 就像 Excel 表格或 SQL 表,是 Pandas 中最常用的数据结构。

3.1 DataFrame 的创建
  • 从字典创建:字典的键是列名,值是列数据。
data = {
    'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
    'year': [2000, 2001, 2002, 2001, 2002],
    'pop': [1.5, 1.7, 3.6, 2.4, 2.9]
}
frame = pd.DataFrame(data)
  • 自定义列顺序:传入 columns 参数。
pd.DataFrame(data, columns=['year', 'state', 'pop'])
  • 带索引:传入 index 参数。
frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'], index=['one', 'two', 'three', 'four', 'five'])
# 注意:如果列名在字典中不存在(如 'debt'),该列会初始化为 NaN。
3.2 DataFrame 的属性
  • frame.columns:获取列名索引。

  • frame.index:获取行索引。

  • frame.values:获取底层的二维 NumPy 数组。

3.3 列的操作
  • 获取列

    • frame['state'] (返回一个 Series)。

    • frame.state (属性访问法,仅限简单列名)。

  • 修改列

    • frame['debt'] = 16.5 (赋值给整列)。

    • frame['debt'] = np.arange(5.) (赋值给数组)。

  • 添加新列:如果列不存在,直接赋值即可创建新列。

  • 删除列del frame['debt']

3.4 行的操作 (索引)
  • 获取行

    • 位置索引frame.iloc[1] (获取第 2 行)。

    • 标签索引frame.loc['two'] (获取标签为 ‘two’ 的行)。

  • 切片行frame[:2] (获取前两行)。


基本功能与运算

认识了 Series 和 DataFrame 两种结构后,接下来是它们通用的基本操作。

4.1 重新索引

reindex() 方法用于创建一个新对象,其数据符合新索引。

obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])
obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])
# 输出:e 对应的值为 NaN (缺失值)
  • 填充缺失值:使用 method='ffill' (前向填充) 或 method='bfill' (后向填充)。
4.2 丢弃条目

drop() 方法用于删除行或列。

# 删除行
obj.drop('a')
# 删除列 (axis=1)
frame.drop(['year', 'state'], axis=1)
4.3 算术运算与对齐

Pandas 在进行算术运算时会自动对齐索引

  • Series 间运算:索引对齐,缺失值产生 NaN。

  • DataFrame 间运算:列和行索引都会对齐。

  • 广播机制:类似 NumPy,可以沿着行或列进行广播。


数据清洗与处理

真实数据很少是干净的,这一节整理缺失值处理和层次化索引这两个常见问题。

5.1 处理缺失值
  • 检测isnull() / notnull()

  • 删除dropna()

    • how='all':只删除全为 NaN 的行/列。

    • thresh=2:保留至少有 2 个非 NaN 值的行。

  • 填充fillna()

    • fillna(0):填充 0。

    • fillna(method='ffill'):前向填充。

    • fillna({'A': 0, 'B': 1}):按列填充不同值。

5.2 层次化索引

Pandas 允许拥有多个(层级)索引。

# 创建 MultiIndex
arrays = [['bar', 'bar', 'baz', 'baz'], ['one', 'two', 'one', 'two']]
index = pd.MultiIndex.from_arrays(arrays, names=['first', 'second'])
s = pd.Series(np.random.randn(4), index=index)

# 选择数据
s['bar']          # 选择第一层索引
s.loc # 选择多层索引

总结

这份笔记涵盖了文档中的所有核心概念,建议你结合 Jupyter Notebook 实际运行代码加深理解。

Pandas 高效数据处理实战指南

前一部分梳理了概念,这一部分换成场景导向:每个技巧按「应用场景 — 核心语法 — 原理解析」组织,先从最基础的准备工作开始。

1.1 版本检查 (Version Check)

  • 应用场景:在复现他人代码或排查兼容性问题时,首先确认 Pandas 版本。

  • 核心语法

import pandas as pd
pd.__version__
  • 原理解析:直接访问模块的 version 属性,返回当前安装的版本号字符串。

1.2 快速构建 DataFrame

  • 应用场景:编写教程、测试函数逻辑或创建小型示例数据时,避免读取外部文件。

  • 核心语法

    • 字典法(适合少量结构化数据):
    df = pd.DataFrame({'col_one': [1, 2], 'col_two': [3, 4]})
    • 随机数法(适合大规模性能测试):
    import numpy as np
    df = pd.DataFrame(np.random.rand(4, 8), columns=list('abcdefgh'))
  • 原理解析:利用 Python 字典键值对映射列名与数据,或利用 NumPy 生成随机矩阵快速填充。

1.3 列名标准化 (Renaming Columns)

  • 应用场景:原始数据列名包含空格、特殊字符或大小写不统一,导致代码调用困难。

  • 核心语法

    • 指定重命名
    df.rename({'old name': 'new_name'}, axis='columns', inplace=True)
    # 或简写
    df.rename(columns={'old name': 'new_name'}, inplace=True)
    • 批量格式化(去除空格并转下划线):
    df.columns = df.columns.str.replace(' ', '_').str.lower()
  • 原理解析rename 方法通过字典映射修改标签;.str 访问器允许对 Index 对象进行向量化字符串操作。

1.4 序列反转 (Reversing Order)

  • 应用场景:时间序列数据需要倒序查看,或需要反转行顺序以匹配其他数据源。

  • 核心语法

df_reversed = df.iloc[::-1].reset_index(drop=True)
  • 原理解析

    1. iloc[::-1]:利用 Python 切片步长为 -1 的特性,实现行索引的完全反转。

    2. reset_index(drop=True):反转后原索引顺序错乱,此步骤丢弃旧索引并生成新的连续整数索引。


第二章:数据清洗与类型转换

处理“脏数据”是数据分析中最耗时的环节,本章重点解决类型不一致和格式不规范问题。

2.1 按数据类型筛选列

  • 应用场景:只想对数值列进行统计,或只想对文本列进行清洗,无需手动列出列名。

  • 核心语法

# 仅选择数值型列
df_num = df.select_dtypes(include='number')

# 仅选择对象型(字符串)列
df_obj = df.select_dtypes(include='object')

# 排除特定类型
df_no_float = df.select_dtypes(exclude='float')
  • 原理解析select_dtypes 根据 NumPy 的 dtype 系统自动过滤列,支持 include(包含)和 exclude(排除)逻辑。

2.2 鲁棒的数值转换 (Robust Numeric Conversion)

  • 应用场景:列中混入了非数值字符(如 '-', 'N/A', 'unknown'),直接使用 astype(int) 会报错。

  • 核心语法

# 强制转换,无法转换的变为 NaN,然后填充为 0
df['col'] = pd.to_numeric(df['col'], errors='coerce').fillna(0)
  • 原理解析

    • errors='coerce':遇到解析错误时不抛出异常,而是将其转换为 NaN (Not a Number)。

    • .fillna(0):随后将产生的缺失值填充为默认值(如 0),保证数据完整性。

2.3 缺失值的高级处理

  • 应用场景:数据集中存在大量缺失值,需要根据缺失比例决定是删除列还是保留。

  • 核心语法

    • 统计缺失
    df.isna().sum()  # 每列缺失数量
    • 按阈值删除列(删除缺失率超过 10% 的列):
    threshold = len(df) * 0.9  # 至少保留 90% 的非空值
    df_clean = df.dropna(thresh=threshold, axis='columns')

2.4 字符串拆分扩列

  • 应用场景:单列中包含复合信息(如 “FirstName LastName” 或 “City, State”),需拆分为多列。

  • 核心语法

# 将 'name' 列按空格拆分,expand=True 表示展开为 DataFrame
df[['first', 'last']] = df['name'].str.split(' ', n=1, expand=True)
  • 原理解析.str.split() 默认返回列表,设置 expand=True 后,Pandas 会自动将列表元素展开为独立的列。n=1 限制只分割一次,防止名字中间有空格导致列数过多。

第三章:多源数据整合

当数据分散在多个文件或不同维度时,如何高效合并。

3.1 批量合并多个文件 (行堆叠)

  • 应用场景:日志文件、分月的销售报表等格式相同但存储在不同 CSV 文件中,需合并为一张大表。

  • 核心语法

from glob import glob

# 1. 获取文件列表
files = sorted(glob('data/stocks*.csv'))

# 2. 使用生成器表达式读取并合并
df_all = pd.concat((pd.read_csv(f) for f in files), ignore_index=True)
  • 原理解析

    • glob:利用通配符匹配文件系统路径。

    • ignore_index=True:合并后重新生成连续索引,避免索引重复。

3.2 横向拼接数据 (列合并)

  • 应用场景:同一组样本的不同特征存储在不同的 DataFrame 中,需按行索引对齐合并。

  • 核心语法

df_combined = pd.concat([df_left, df_right], axis='columns')
  • 原理解析axis='columns' (或 axis=1) 指示 Pandas 沿水平方向拼接。Pandas 会自动基于索引(Index)进行对齐,索引不匹配的位置会自动填充 NaN。

第四章:高级筛选与过滤

从海量数据中精准提取子集,超越基础的布尔索引。

4.1 多条件成员资格筛选

  • 应用场景:筛选出属于多个特定类别的行(例如:只看 “Action” 和 “Drama” 类型的电影)。

  • 核心语法

# 推荐写法
mask = df['genre'].isin(['Action', 'Drama', 'Sci-Fi'])
df_filtered = df[mask]

# 不推荐写法 (冗长且易错)
# df[(df.genre == 'Action') | (df.genre == 'Drama') | ...]
  • 原理解析isin() 方法接受一个列表,返回一个布尔 Series,效率远高于链式的 | (OR) 操作,且代码可读性更强。

4.2 基于频率的动态筛选

  • 应用场景:只关注出现频率最高的前 N 个类别,忽略长尾数据。

  • 核心语法

# 1. 计算频次
counts = df['genre'].value_counts()

# 2. 获取前 3 名的索引(即类别名称)
top_3_genres = counts.nlargest(3).index

# 3. 筛选
df_top = df[df['genre'].isin(top_3_genres)]
  • 原理解析:结合 value_counts() 统计频次,nlargest() 快速提取 Top N,最后再次利用 isin() 完成过滤。这是一套标准的“统计 - 提取 - 过滤”组合拳。

第五章:聚合分析与数据重塑

数据的终极价值在于汇总与形态转换,以支持决策和可视化。

5.1 多函数同时聚合

  • 应用场景:分组后需要同时查看总和、平均值、计数等多个统计量。

  • 核心语法

# 对 'item_price' 列同时计算 sum 和 count
result = df.groupby('order_id')['item_price'].agg(['sum', 'count', 'mean'])
  • 原理解析agg() (aggregate) 方法接受函数名列表或字典,允许在一次 groupby 操作中执行多种计算,避免了多次分组带来的性能损耗。

5.2 统计结果的切片查看

  • 应用场景describe() 生成的统计表信息量过大,只需关注其中几行(如 min, max)和几列。

  • 核心语法

# 先描述,再行切片 ['min':'max'],再列切片 ['Pclass':'Parch']
summary = df.describe().loc['min':'max', 'Pclass':'Parch']
  • 原理解析describe() 返回的 DataFrame 其索引是统计量名称(count, mean, std…)。利用 .loc 可以像操作普通数据一样对这些统计结果进行二次筛选。

5.3 透视表化:unstack 操作

  • 应用场景:将多级索引(MultiIndex)的 Series 转换为更易读的宽表(DataFrame),常用于热力图绘制。

  • 核心语法

# 分组计算均值,得到 MultiIndex Series
s = df.groupby(['Sex', 'Pclass'])['Survived'].mean()

# 将最内层索引 ('Pclass') 转为列
df_wide = s.unstack()
  • 原理解析unstack()stack() 的逆运算,它将指定的行索引层级“旋转”为列索引,从而改变数据的维度结构(从长表变宽表)。

5.4 连续变量离散化 (Binning)

  • 应用场景:将连续的数值(如年龄、收入)划分为离散的区间(如“青年”、“中年”、“老年”),以便进行分组分析。

  • 核心语法

# 定义 bins 边界和对应的标签
bins = [0, 18, 35, 60, 100]
labels = ['Child', 'Young Adult', 'Adult', 'Senior']

df['Age_Group'] = pd.cut(df['Age'], bins=bins, labels=labels, right=False)
  • 原理解析pd.cut() 根据给定的边界将数值映射到对应的区间标签中。生成的新列是 Categorical 类型,非常适合用于 groupby 操作。参数 right=False 表示区间为左闭右开 [ ),可根据需求调整。

📝 学习建议