Python 数据分析入门系列(二):Pandas 数据处理完全指南

🎯 适合人群:有 NumPy 基础、想系统学习 Pandas 的开发者
⏱️ 阅读时间:约 35 分钟
💬 阅读建议:这是我啃完 Pandas 官方文档 + 踩了无数坑后的总结,强烈建议配合 SQL 知识一起理解


来啦来啦,Pandas它来啦。

为什么学 Pandas?

学完 NumPy 后,我当时的想法是:终于搞定数组了,可以开始处理数据了吧?

然后我就打开了一个 CSV 文件……

import numpy as np

# 用 NumPy 读取 CSV
data = np.loadtxt('sales.csv', delimiter=',', skiprows=1)

然后我发现:

  • 列名没了(得单独处理表头)
  • 日期列变成了字符串(得手动转换)
  • 有缺失值就报错(得先清洗)
  • 想按"地区"分组?自己写循环吧

那一刻我明白了:NumPy 是底层引擎,但 Pandas 才是方向盘。

Pandas 建立在 NumPy 之上,专门解决"真实世界的数据都是脏的"这个问题。它提供了:

  • 带标签的二维表格(DataFrame)
  • 缺失值处理
  • 数据对齐和合并
  • 时间序列支持
  • 分组聚合

最重要的是:Pandas 的思维方式和 SQL 非常像。如果你会 SQL,学 Pandas 会事半功倍。


一、Pandas 是什么?

Pandas 的名字来自"Panel Data"(面板数据),核心就两个数据结构:

import pandas as pd
import numpy as np

再啰嗦一句,用pd表示pandas,用np表示numpy,全世界大多数的程序员都这么干,请不要把创意发挥在这里。在程序员业内有个设计铁律叫做 约定优于配置,再全面的配置设计,也不如约定俗称的共识,规范是大家一起遵守的。

1. Series:一维带标签数组

# 从列表创建
s = pd.Series([1, 2, 3, 4, 5])
# 0    1
# 1    2
# 2    3
# 3    4
# 4    5

# 带自定义索引
s = pd.Series([10, 20, 30], index=['a', 'b', 'c'])
# a    10
# b    20
# c    30

# 从字典创建(键自动变成索引)
s = pd.Series({'北京': 2154, '上海': 2428, '广州': 1530})

💡 我的理解:Series = NumPy 数组 + 索引标签。可以把它想象成 Excel 的一列,或者 SQL 的一个字段。

2. DataFrame:二维表格

# 从字典创建(最常用)
df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五'],
    '年龄': [25, 30, 28],
    '城市': ['北京', '上海', '广州'],
    '薪资': [15000, 20000, 18000]
})

# 从列表嵌套创建
df = pd.DataFrame([
    ['张三', 25, '北京', 15000],
    ['李四', 30, '上海', 20000],
    ['王五', 28, '广州', 18000]
], columns=['姓名', '年龄', '城市', '薪资'])

# 从 CSV 读取(实际工作 90% 的场景)
df = pd.read_csv('employees.csv')

📊 DataFrame 的本质:多个 Series 共享同一个索引。可以把它想象成 SQL 的一张表,或者 Excel 的一个 Sheet。


二、核心概念:索引(Index)

这是 Pandas 最重要、也最容易混淆的概念。 列表的索引是下标,表的索引是主键,学过数据结构的朋友都知道,索引的存在是为了高效定位数据,pandas作为最流行的python数据分析包,也有高效的索引设计。

1. 索引是什么?

df = pd.DataFrame({
    '产品': ['A', 'B', 'C', 'D'],
    '销量': [100, 200, 150, 300]
})

print(df.index)  # RangeIndex(start=0, stop=4, step=1)
print(df.columns)  # Index(['产品', '销量'], dtype='object')
  • 行索引(index):标识每一行,默认是 0, 1, 2…
  • 列索引(columns):标识每一列,就是你起的列名

2. 设置索引

# 读取时就指定
df = pd.read_csv('sales.csv', index_col='订单 ID')

# 或者事后设置
df = df.set_index('产品')

# 多重索引(类似 SQL 的复合主键)
df = df.set_index(['地区', '产品'])

🤔 什么时候需要自定义索引?

  • 用有意义的字段做索引(如用户 ID、订单号)
  • 加速查找(索引过的列查询更快)
  • 对齐数据时自动匹配

3. 索引操作

# 重置索引(把索引变回普通列,group by操作后比较常见)
df = df.reset_index()

# 修改索引 (数据下标索引不直观的时候比较有用)
df.index = ['a', 'b', 'c', 'd']

# 索引重命名
df = df.rename(index={'a': 'A'}, columns={'销量': '销售量'})

三、数据读取与导出

实际工作中,数据读取是最常见的操作。Pandas 支持的数据格式多到离谱:

1. 读取数据

# CSV(最常用)
df = pd.read_csv('data.csv')
df = pd.read_csv('data.csv', encoding='utf-8')  # 指定编码
df = pd.read_csv('data.csv', usecols=['列 1', '列 2'])  # 只读部分列
df = pd.read_csv('data.csv', nrows=1000)  # 只读前 1000 行
df = pd.read_csv('data.csv', parse_dates=['日期列'])  # 自动解析日期

# Excel
df = pd.read_excel('data.xlsx', sheet_name='Sheet1')

# SQL 数据库
import sqlite3
conn = sqlite3.connect('database.db')
df = pd.read_sql('SELECT * FROM users', conn)

# JSON
df = pd.read_json('data.json')

# 剪贴板(从 Excel 复制后直接粘贴)
df = pd.read_clipboard()

# HTML 表格(爬表神器)
dfs = pd.read_html('https://example.com/page.html')  # 返回所有表格的列表

2. 导出数据

df.to_csv('output.csv', index=False)  # index=False 不保存索引
df.to_excel('output.xlsx', sheet_name='结果')
df.to_json('output.json')
df.to_sql('table_name', conn, if_exists='replace')

💡 经验:读取大文件时用 chunksize 参数分块处理,避免内存爆炸。


四、数据选择:Pandas 的"SELECT"

这部分和 SQL 的 SELECT 最像,但语法更丰富(也更复杂)。

1. 选择列(SELECT column)

# SQL: SELECT 姓名 FROM employees

# 单列(返回 Series)
df['姓名']

# 多列(返回 DataFrame)
df[['姓名', '薪资']]

2. 选择行(WHERE)

# SQL: SELECT * FROM employees WHERE 年龄 > 28

# 布尔索引(最常用)
df[df['年龄'] > 28]

# 多条件
df[(df['年龄'] > 28) & (df['薪资'] > 15000)]  # 注意用 & 不是 and
df[(df['城市'] == '北京') | (df['城市'] == '上海')]  # 用 | 不是 or
df[~(df['城市'] == '广州')]  # 用 ~ 不是 not,排除广州

行和列的选择,在Pandas里的写法都是dataframe[选择条件], 列选择直接写列名,行选择就用到Numpy里提到过的布尔索引的写法。

3. loc 和 iloc:定位神器

这是比较容易混淆的地方,必须说清楚,可以看下括号全称记一下:

  • loc:基于标签(label)— 用索引名
  • iloc:基于位置(integer location)— 用数字下标
# SQL: SELECT 姓名,薪资 FROM employees WHERE 索引 = 2

df = df.set_index('姓名')  # 假设索引现在是姓名

# loc:用标签
df.loc['张三']           # 选张三这行
df.loc['张三', '薪资']    # 张三的薪资
df.loc[:, ['姓名', '薪资']]  # 所有行的姓名和薪资列

# iloc:用位置
df.iloc[0]              # 第一行
df.iloc[0, 3]           # 第一行第四列
df.iloc[0:3, 1:3]       # 前 3 行,第 2-3 列(左闭右开!)

⚠️ 血泪教训loc 的切片是闭区间(包含结束值),iloc左闭右开(不包含结束值)。这个坑我踩过至少三次。

df.loc[0:2]    # 包含第 0、1、2 行(三行)
df.iloc[0:2]   # 只包含第 0、1 行(两行)

4. 条件选择进阶

# isin:SQL 的 IN
df[df['城市'].isin(['北京', '上海'])]

# str.contains:SQL 的 LIKE
df[df['姓名'].str.contains('张')]  # 姓名包含"张"
df[df['姓名'].str.startswith('王')]  # 以"王"开头
df[df['姓名'].str.endswith('五')]  # 以"五"结尾

# between:SQL 的 BETWEEN
df[df['年龄'].between(25, 30)]

# query:用字符串写条件(适合复杂条件)
df.query('年龄 > 28 and 薪资 > 15000')
df.query('城市 in ["北京", "上海"]')

💡 query 的好处:代码更简洁,不需要反复写 df['列名']


5. 与 NumPy 配合使用

Pandas 建立在 NumPy 之上,两者配合使用非常常见:

import numpy as np

# np.where:条件赋值(类似 Excel 的 IF)
# SQL: CASE WHEN 年龄 > 30 THEN '中年' ELSE '青年' END
df['年龄段'] = np.where(df['年龄'] > 30, '中年', '青年')

# 嵌套 np.where(多层条件)
df['薪资等级'] = np.where(
    df['薪资'] > 20000, '高',
    np.where(df['薪资'] > 10000, '中', '低')
)

# np.select:多条件选择(比嵌套 where 更清晰)
conditions = [
    df['薪资'] > 20000,
    df['薪资'] > 10000,
    df['薪资'] <= 10000
]
choices = ['高', '中', '低']
df['薪资等级'] = np.select(conditions, choices)

# np.nan:创建带缺失值的列
df['备注'] = np.nan

# np.random:生成随机数据
df['随机数'] = np.random.rand(len(df))
df['随机整数'] = np.random.randint(1, 100, len(df))

# np.log/np.sqrt:数值转换
df['薪资_log'] = np.log(df['薪资'])  # 对数转换,适合偏态分布
df['薪资_sqrt'] = np.sqrt(df['薪资'])

# np.percentile:分位数截断
threshold = np.percentile(df['薪资'], 95)  # 95 分位数
df = df[df['薪资'] <= threshold]  # 去掉最高 5% 的异常值

# np.clip:限制范围
df['薪资_截断'] = np.clip(df['薪资'], 5000, 50000)  # 低于 5000 变 5000,高于 50000 变 50000

🤔 什么时候用 np.where 而不是 apply?

  • np.where:向量化操作,速度快,适合简单条件
  • apply:可以写复杂逻辑,但速度慢

经验法则:能用 np.where 就别用 apply

# 性能对比(10 万行数据)
# np.where:约 0.5ms
df['等级'] = np.where(df['薪资'] > 15000, '高', '低')

# apply:约 50ms(慢 100 倍!)
df['等级'] = df['薪资'].apply(lambda x: '高' if x > 15000 else '低')

💡 我的经验:数据清洗时,np.where + np.select 能解决 80% 的条件赋值需求,而且比 apply 快得多。


五、数据清洗:Pandas 的超能力

”真实世界的数据都是脏的“。数据清洗是 Pandas 最了不起的功能。

1. 缺失值处理

# 检查缺失值
df.isnull()        # 返回布尔 DataFrame
df.isnull().sum()  # 每列缺失值数量
df.notnull()       # 检查非缺失值

# SQL 类比:IS NULL / IS NOT NULL

# 删除缺失值
df.dropna()                    # 删除任何有缺失值的行
df.dropna(axis=1)              # 删除有缺失值的列
df.dropna(thresh=3)            # 保留至少有 3 个非缺失值的行
df.dropna(subset=['姓名'])     # 只考虑姓名列

# 填充缺失值
df.fillna(0)                   # 填 0
df.fillna(df.mean())           # 填均值
df.fillna(method='ffill')      # 用前一个值填充(forward fill)
df.fillna(method='bfill')      # 用后一个值填充(backward fill)
df['年龄'].fillna(df['年龄'].median(), inplace=True)  # 填中位数

# 插值(适合时间序列)
df.interpolate()

🤔 我的经验:缺失值怎么处理取决于业务。可以删除缺省值,数值型可以用均值/中位数,分类变量可以用"未知",时间序列用插值等,甚至必要时用模型预测值填充。

2. 重复值处理

# SQL: SELECT DISTINCT / GROUP BY

df.duplicated()           # 检查重复行
df.duplicated().sum()     # 重复行数量
df.drop_duplicates()      # 删除重复行
df.drop_duplicates(subset=['姓名'])  # 按姓名列去重
df.drop_duplicates(subset=['姓名'], keep='last')  # 保留最后一次出现的

3. 数据类型转换

# 查看数据类型
df.dtypes
df['年龄'].dtype

# 转换类型
df['年龄'] = df['年龄'].astype(int)
df['日期'] = pd.to_datetime(df['日期'])
df['薪资'] = pd.to_numeric(df['薪资'], errors='coerce')  # 无法转换的变 NaN

# 类别类型(节省内存)
df['城市'] = df['城市'].astype('category')

💡 类别类型:如果一列只有几个固定值(如城市、性别),用 category 类型可以大幅节省内存。

4. 字符串处理

# Pandas 的 str 访问器(向量化字符串操作)
df['姓名'].str.upper()           # 转大写
df['姓名'].str.lower()           # 转小写
df['姓名'].str.strip()           # 去空格
df['姓名'].str.split(' ')        # 分割
df['姓名'].str.replace('张', '王')  # 替换
df['姓名'].str.extract('(\d+)')  # 正则提取
df['姓名'].str.len()             # 字符串长度
df['姓名'].str.startswith('张')   # 是否以...开头

5. 应用函数

# apply:对行或列应用自定义函数
df['年龄组'] = df['年龄'].apply(lambda x: '青年' if x < 30 else '中年')

def 薪资等级(salary):
    if salary < 10000:
        return '初级'
    elif salary < 20000:
        return '中级'
    else:
        return '高级'

df['等级'] = df['薪资'].apply(薪资等级)

# applymap:对 DataFrame 每个元素应用函数(Pandas 2.1+ 用 map)
df = df.map(lambda x: str(x).strip() if isinstance(x, str) else x)

# transform:分组后转换(后面分组聚合会讲)

⚠️ 性能提示apply 本质是循环,大数据量时尽量用向量化操作。


六、数据运算:Pandas 的"表达式"

1. 列运算

# 新增列
df['年薪'] = df['薪资'] * 12
df['姓名长度'] = df['姓名'].str.len()

# 修改列
df['薪资'] = df['薪资'] * 1.1  # 涨薪 10%

# 删除列
df.drop('临时列', axis=1, inplace=True)
del df['临时列']  # 另一种删除方式

2. 排序

# SQL: ORDER BY

df.sort_values('薪资')              # 升序
df.sort_values('薪资', ascending=False)  # 降序
df.sort_values(['城市', '薪资'], ascending=[True, False])  # 多列排序
df.sort_index()                     # 按索引排序

3. 排名

df['薪资排名'] = df['薪资'].rank()           # 平均排名
df['薪资排名'] = df['薪资'].rank(method='first')  # 第一名就是第一名
df['薪资排名'] = df['薪资'].rank(ascending=False)  # 降序排名

七、分组聚合:Pandas 的"GROUP BY"

这是数据分析最核心的操作,没有之一。

1. 基础分组

# SQL: SELECT 城市,AVG(薪资) FROM employees GROUP BY 城市

# 单列分组
df.groupby('城市')['薪资'].mean()

# 多列分组
df.groupby(['城市', '部门'])['薪资'].mean()

# 多个聚合函数
df.groupby('城市')['薪资'].agg(['mean', 'sum', 'count', 'min', 'max'])

# 不同列不同聚合
df.groupby('城市').agg({
    '薪资': 'mean',
    '年龄': 'sum',
    '姓名': 'count'
})

# 自定义聚合
df.groupby('城市')['薪资'].agg(lambda x: x.max() - x.min())  # 极差

2. 分组后过滤

# SQL: HAVING

# 只保留平均薪资>15000 的城市
df.groupby('城市').filter(lambda x: x['薪资'].mean() > 15000)

# 只保留人数>5 的组
df.groupby('部门').filter(lambda x: len(x) > 5)

3. 分组后转换

# transform:返回和原数据同样长度的结果

# 计算每个人薪资与所在城市平均值的差
df['薪资 - 城市平均'] = df.groupby('城市')['薪资'].transform(lambda x: x - x.mean())

# 标准化(每组内)
df['标准化薪资'] = df.groupby('部门')['薪资'].transform(
    lambda x: (x - x.mean()) / x.std()
)

# 填充组内缺失值
df['薪资'] = df.groupby('部门')['薪资'].transform(lambda x: x.fillna(x.mean()))

🤔 agg vs transform 的区别

  • agg:每组返回一个值(行数减少)
  • transform:每组返回和原数据同样长度的结果(行数不变)

4. 透视表

# SQL: PIVOT / CASE WHEN

# 基础透视表
pd.pivot_table(df, values='薪资', index='城市', columns='部门', aggfunc='mean')

# 多个值
pd.pivot_table(df, values=['薪资', '年龄'], index='城市', aggfunc='mean')

# 添加总计
pd.pivot_table(df, values='薪资', index='城市', aggfunc='mean', margins=True)

八、数据合并:Pandas 的"JOIN"

这部分和 SQL 的 JOIN 几乎一一对应。

1. merge:数据库风格的合并

# SQL: SELECT * FROM employees e JOIN departments d ON e.dept_id = d.id

employees = pd.DataFrame({
    '姓名': ['张三', '李四'],
    '部门 ID': [1, 2]
})

departments = pd.DataFrame({
    '部门 ID': [1, 2, 3],
    '部门名': ['技术', '产品', '设计']
})

# 内连接(INNER JOIN)
pd.merge(employees, departments, on='部门 ID', how='inner')

# 左连接(LEFT JOIN)
pd.merge(employees, departments, on='部门 ID', how='left')

# 右连接(RIGHT JOIN)
pd.merge(employees, departments, on='部门 ID', how='right')

# 外连接(FULL OUTER JOIN)
pd.merge(employees, departments, on='部门 ID', how='outer')

# 多键合并
pd.merge(df1, df2, on=['键 1', '键 2'])

# 不同列名合并
pd.merge(employees, departments, left_on='部门 ID', right_on='id')

2. join:基于索引的合并

# 类似 merge,但基于索引
df1.join(df2, how='left')

3. concat:堆叠

# SQL: UNION / UNION ALL

# 垂直堆叠(行增加)
pd.concat([df1, df2], axis=0)

# 水平堆叠(列增加)
pd.concat([df1, df2], axis=1)

# 忽略索引
pd.concat([df1, df2], ignore_index=True)

# 添加键标识来源
pd.concat([df1, df2], keys=['来源 1', '来源 2'])

📝 merge vs concat

  • merge:基于键的关联(类似 SQL JOIN)
  • concat:简单的堆叠(类似 SQL UNION)

九、时间序列:Pandas 的杀手锏

Pandas 的时间序列功能强大到可以单独写一本书。这里只讲最常用的。

1. 创建时间序列

# 转换日期列
df['日期'] = pd.to_datetime(df['日期'])
df['日期'] = pd.to_datetime(df['日期'], format='%Y-%m-%d')

# 当前时间
pd.Timestamp.now()

# 日期范围
pd.date_range('2024-01-01', '2024-12-31', freq='D')  # 每天
pd.date_range('2024-01-01', periods=12, freq='M')    # 12 个月

# 设置日期索引
df = df.set_index('日期')

2. 时间属性

# 提取时间组件
df.index.year
df.index.month
df.index.day
df.index.dayofweek  # 0=周一,6=周日
df.index.hour
df.index.quarter

# 布尔筛选
df[df.index.month == 1]  # 只选 1 月
df[df.index.dayofweek < 5]  # 只选工作日

3. 重采样

# SQL: DATE_TRUNC / GROUP BY 时间

# 日数据聚合为月数据
df.resample('M').mean()  # 按月平均
df.resample('M').sum()   # 按月求和
df.resample('Q').mean()  # 按季度
df.resample('Y').mean()  # 按年

# 降采样(高频→低频)
df.resample('W').mean()  # 日→周

# 升采样(低频→高频)
df.resample('D').ffill()  # 月→日,用前值填充

4. 移动窗口

# 移动平均
df['7 日移动平均'] = df['销量'].rolling(window=7).mean()

# 指数加权移动平均
df['指数平均'] = df['销量'].ewm(span=7).mean()

# 累积
df['累积销量'] = df['销量'].cumsum()

十、实战示例

示例 1:销售数据分析

📄 sales.csv 长这样(前 5 行):

日期,地区,产品,销售额,数量
2024-01-05,华北,笔记本电脑,8500,2
2024-01-05,华东,鼠标,120,5
2024-01-06,华南,键盘,350,3
2024-01-06,华北,显示器,2200,1
2024-01-07,华东,笔记本电脑,8500,1
# 读取数据
sales = pd.read_csv('sales.csv', parse_dates=['日期'])

# 数据清洗
sales = sales.drop_duplicates()
sales['销售额'] = pd.to_numeric(sales['销售额'], errors='coerce')
sales = sales.dropna(subset=['销售额'])

# 按月统计
monthly = sales.resample('M', on='日期')['销售额'].sum()

# 按地区和产品分组
grouped = sales.groupby(['地区', '产品'])['销售额'].agg(['sum', 'mean', 'count'])

# 计算每个产品的销售占比
total = sales['销售额'].sum()
sales['占比'] = sales['销售额'] / total

# 找出 top 10 产品
top10 = sales.groupby('产品')['销售额'].sum().nlargest(10)

示例 2:用户行为分析

📄 users.csv

用户 ID,姓名,注册时间,城市
001,张三,2023-01-15,北京
002,李四,2023-02-20,上海
003,王五,2023-03-10,广州

📄 orders.csv

订单 ID,用户 ID,订单日期,金额
1001,001,2024-01-05,299
1002,001,2024-02-10,599
1003,002,2024-01-20,1299
1004,003,2024-03-15,89
# 读取用户数据
users = pd.read_csv('users.csv')
orders = pd.read_csv('orders.csv', parse_dates=['订单日期'])

# 合并数据
user_orders = pd.merge(users, orders, on='用户 ID', how='left')

# 计算每个用户的订单数和总金额
user_stats = user_orders.groupby('用户 ID').agg({
    '订单 ID': 'count',
    '金额': 'sum'
}).rename(columns={'订单 ID': '订单数', '金额': '总金额'})

# RFM 分析
rfm = user_orders.groupby('用户 ID').agg({
    '订单日期': 'max',  # Recency
    '订单 ID': 'count',  # Frequency
    '金额': 'sum'  # Monetary
})

# 计算最近一次购买距离今天的天数
rfm['R'] = (pd.Timestamp.now() - rfm['订单日期']).dt.days
rfm = rfm.rename(columns={'订单 ID': 'F', '金额': 'M'})

示例 3:时间序列预测准备

# 创建完整日期范围
full_dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')

# 重采样到完整日期,填充缺失
df = df.set_index('日期')
df = df.reindex(full_dates)
df = df.fillna(method='ffill')  # 用前值填充

# 添加时间特征
df['year'] = df.index.year
df['month'] = df.index.month
df['dayofweek'] = df.index.dayofweek
df['is_weekend'] = df.index.dayofweek >= 5

# 添加滞后特征(用昨天的值预测今天)
df['销量_滞后 1'] = df['销量'].shift(1)
df['销量_滞后 7'] = df['销量'].shift(7)  # 上周同一天

# 添加移动平均
df['销量_7 日均'] = df['销量'].rolling(7).mean()

十一、性能优化

1. 数据类型优化

# 查看内存占用
df.info(memory_usage='deep')

# 优化整数类型
df['年龄'] = df['年龄'].astype('int8')  # 如果年龄<127

# 优化分类变量
df['城市'] = df['城市'].astype('category')

# 优化后对比
df.info(memory_usage='deep')  # 可能节省 50-80% 内存

2. 避免 iterrows

# ❌ 糟糕:逐行迭代(慢)
for index, row in df.iterrows():
    df.loc[index, '新列'] = row['列 1'] * 2

# ✅ 正确:向量化
df['新列'] = df['列 1'] * 2

# ✅ 或者:apply
df['新列'] = df['列 1'].apply(lambda x: x * 2)

3. 分块处理大文件

# 读取大 CSV
chunks = pd.read_csv('huge_file.csv', chunksize=10000)

for chunk in chunks:
    # 处理每个块
    result = chunk.groupby('类别')['值'].sum()
    # 保存或累加结果

十二、常见坑与最佳实践

⚠️ 常见坑(都是我的血泪史)

# 坑 1:SettingWithCopyWarning
df[df['年龄'] > 28]['薪资'] = 0  # 警告!这是链式索引

# 正确写法:
df.loc[df['年龄'] > 28, '薪资'] = 0

# 坑 2:inplace 的陷阱
df.dropna()  # 不会修改原 df!
# 正确写法
df = df.dropna()  # 或者
df.dropna(inplace=True)

# 坑 3:布尔索引的括号
df[df['年龄'] > 28 & df['薪资'] > 15000]  # 错误!
df[(df['年龄'] > 28) & (df['薪资'] > 15000)]  # 正确

# 坑 4:日期解析
df = pd.read_csv('data.csv')  # 日期列是字符串
df = pd.read_csv('data.csv', parse_dates=['日期'])  # 正确

# 坑 5:groupby 后索引变化
grouped = df.groupby('城市')['薪资'].mean()
# grouped 的索引是"城市",不是数字
grouped = grouped.reset_index()  # 需要时重置

✅ 最佳实践

  1. 读取时就指定数据类型,避免后续转换
  2. loc 而不是链式索引
  3. 优先向量化,其次 apply,最后 iterrows
  4. 大文件用 chunksize 分块
  5. 分类变量用 category 类型
  6. 链式操作时注意中间结果
  7. 学会看 df.info()df.describe()

十三、Pandas vs SQL 对照表

操作SQLPandas
选择列SELECT a, bdf[['a', 'b']]
过滤WHERE a > 5df[df['a'] > 5]
排序ORDER BY a DESCdf.sort_values('a', ascending=False)
分组GROUP BY adf.groupby('a')
聚合AVG(b).agg('mean')
连接JOIN ... ONpd.merge(..., on=...)
去重DISTINCTdrop_duplicates()
限制行数LIMIT 10head(10)
空值判断IS NULLisnull()
日期截断DATE_TRUNC('month', date)df.resample('M')

总结

学完 Pandas 后,我的感受是:

NumPy 是引擎,Pandas 是整车。:Numpy提供了高效的底层数据结构,足够优秀的引擎,Pandas加上多样的功能组装,提供了丰富的API,成了一辆飞驰的车。

NumPy 给你原始的计算能力,Pandas 把这些能力包装成好用的工具,让你能专注于数据分析本身,而不是数据清洗的细节。

核心就三件事:

概念作用关键方法
DataFrame二维表格数据read_csv, DataFrame()
索引数据定位和对齐set_index, loc, iloc
分组聚合数据汇总分析groupby, agg, pivot_table

和 SQL 的类比是学习捷径,但记住 Pandas 比 SQL 更灵活:

  • 可以逐行操作(虽然不推荐)
  • 可以应用任意 Python 函数
  • 时间序列处理更强大

🤓 最后送一句话:Pandas 的 API 多到记不住是正常的。记不住就经常查文档。关键是理解核心概念,具体函数用时再查。


📚 参考资料:

  • Pandas 官方文档:https://pandas.pydata.org/docs/
  • 10 Minutes to Pandas:https://pandas.pydata.org/docs/user_guide/10min.html
  • Pandas Cookbook:https://pandascookbook.com/
  • 本书代码仓库:自己敲一遍比较好👀

下期预告:Matplotlib 数据可视化 📊


如果这篇博客对你有帮助,欢迎分享!也欢迎在评论区交流你学 Pandas 时踩过的坑~

更多推荐