Python 数据分析入门系列(二):Pandas 数据处理完全指南
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() # 需要时重置
✅ 最佳实践
- 读取时就指定数据类型,避免后续转换
- 用
loc而不是链式索引 - 优先向量化,其次 apply,最后 iterrows
- 大文件用
chunksize分块 - 分类变量用
category类型 - 链式操作时注意中间结果
- 学会看
df.info()和df.describe()
十三、Pandas vs SQL 对照表
| 操作 | SQL | Pandas |
|---|---|---|
| 选择列 | SELECT a, b | df[['a', 'b']] |
| 过滤 | WHERE a > 5 | df[df['a'] > 5] |
| 排序 | ORDER BY a DESC | df.sort_values('a', ascending=False) |
| 分组 | GROUP BY a | df.groupby('a') |
| 聚合 | AVG(b) | .agg('mean') |
| 连接 | JOIN ... ON | pd.merge(..., on=...) |
| 去重 | DISTINCT | drop_duplicates() |
| 限制行数 | LIMIT 10 | head(10) |
| 空值判断 | IS NULL | isnull() |
| 日期截断 | 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 时踩过的坑~
更多推荐
所有评论(0)