Pandas 数据处理实战:从基础操作到数据清洗
1. 从零开始:为什么说Pandas是数据处理的神器?
如果你刚开始接触数据分析,或者经常被Excel里成千上万行的数据搞得头晕眼花,那你一定听说过Pandas。我第一次用它的时候,感觉就像从手动挖土升级到了开挖掘机。Pandas不是那种只会出现在教科书里的“屠龙之技”,而是你每天处理表格、清洗数据、做简单分析时,真正能派上用场的瑞士军刀。
简单来说,Pandas是一个基于Python的、专门为数据分析而生的库。它的核心是两种数据结构:Series和DataFrame。你可以把Series想象成Excel里单独的一列,有数据也有对应的标签(索引)。而DataFrame,就是整个Excel工作表,一个标准的二维表格,有行有列。几乎所有你能想到的表格操作,比如筛选、排序、合并、计算统计值,Pandas都能用一两行代码轻松搞定。最棒的是,它能处理的数据量远超Excel的极限,而且可以和Python生态里其他强大的库(比如画图的Matplotlib、做机器学习的Scikit-learn)无缝衔接。
很多新手会问,我直接用Python的列表和字典不行吗?当然可以,但效率天差地别。举个例子,你想在几万行数据里,找出所有“年龄大于30且销售额超过1万”的记录。用纯Python写循环,代码又长又慢。用Pandas,可能就是一行 df[(df[‘年龄’] > 30) & (df[‘销售额’] > 10000)],清晰直观,执行速度还快得多。这就是Pandas的魅力:它用起来像在思考,而不是在编程。
安装Pandas非常简单,打开你的命令行(终端或Anaconda Prompt),输入 pip install pandas 即可。通常我们会配合NumPy一起使用,所以也可以一并安装:pip install pandas numpy。在代码里,我们习惯用 import pandas as pd 来导入,pd 这个别名是社区约定俗成的,用起来特别方便。接下来,我们就从最核心的这两个数据结构开始,一步步上手。
2. 核心基石:彻底搞懂Series和DataFrame
2.1 Series:你的第一个带标签的数组
Series是Pandas里的一维数据结构,你可以把它理解为一个“智能列表”。它由两部分组成:数据值和与之对应的索引。索引就是每个数据的“名字”或“标签”,这让我们能像查字典一样,用有意义的标签来获取数据,而不仅仅是靠位置(第0个、第1个)。
创建Series就像喝水一样简单。最直接的方式是从一个Python列表开始:
import pandas as pd
# 从列表创建,不指定索引,Pandas会自动生成0,1,2...的数字索引
s1 = pd.Series([10, 20, 30, 40])
print(s1)
输出会是:
0 10
1 20
2 30
3 40
dtype: int64
左边那列0,1,2,3就是自动生成的索引。但这样不够酷,我们完全可以自定义有意义的索引:
# 创建时指定索引
s2 = pd.Series([10, 20, 30, 40], index=[‘第一季度’, ‘第二季度’, ‘第三季度’, ‘第四季度’])
print(s2)
输出:
第一季度 10
第二季度 20
第三季度 30
第四季度 40
dtype: int64
现在,你可以用 s2[‘第二季度’] 来获取值20,这比 s1[1] 直观太多了。Series也可以直接从字典创建,字典的键会自动变成索引:
# 从字典创建
sales_data = {‘北京’: 1500, ‘上海’: 1800, ‘广州’: 1200, ‘深圳’: 2000}
s3 = pd.Series(sales_data)
print(s3)
Series有一系列属性可以帮你快速了解数据概况。我刚开始用的时候,经常靠这几个属性来“摸清”数据的底细:
s.values: 看看数据本身是什么,返回一个NumPy数组。s.index: 看看索引长什么样。s.dtype: 看看数据类型是整数、浮点数还是字符串,这对后续计算很重要。s.shape: 看看数据有多少个元素,对于Series,返回的是一个元组,比如(4,)表示有4个数据。s.size: 直接告诉你总共有多少个数据点。
2.2 DataFrame:数据分析的主战场
如果说Series是单兵作战,那DataFrame就是集团军。它是我们最常用、功能最强大的二维表格结构。你可以把它看作一个字典的集合,其中每个键(列名)对应一个Series(列数据)。
创建DataFrame的方法非常灵活。我最常用的是从一个“字典列表”或者“列表字典”开始。比如,我们有几个员工的记录:
# 方法1:字典的键是列名,值是列表(代表该列的所有数据)
data = {
‘姓名’: [‘张三’, ‘李四’, ‘王五’],
‘年龄’: [28, 34, 25],
‘部门’: [‘技术部’, ‘市场部’, ‘技术部’],
‘薪资’: [15000, 12000, 13000]
}
df = pd.DataFrame(data)
print(df)
输出一个整洁的表格:
姓名 年龄 部门 薪资
0 张三 28 技术部 15000
1 李四 34 市场部 12000
2 王五 25 技术部 13000
左边的0,1,2是行索引。DataFrame的属性同样实用:
df.columns: 查看所有列名。df.index: 查看行索引。df.shape: 返回(行数, 列数),一眼看清数据规模。df.dtypes: 查看每一列的数据类型,避免把字符串当数字算。df.head(): 默认查看前5行,快速预览数据。df.tail(3)则查看最后3行。
从DataFrame里取数据是基本功,有几种核心方法你必须掌握:
- 取一列:
df[‘姓名’]或df.姓名(注意,如果列名有空格或特殊字符,只能用第一种)。 - 取多列:
df[[‘姓名’, ‘薪资’]],注意是两个方括号。 - 按行标签取:使用
loc。比如我们设置索引为姓名:df.set_index(‘姓名’, inplace=True),然后就可以用df.loc[‘张三’]获取张三的所有信息。 - 按行位置取:使用
iloc。df.iloc[0]取第一行,df.iloc[0:2]取前两行(切片)。 - 条件筛选:这是Pandas的精华。
df[df[‘年龄’] > 30]筛选出年龄大于30的行。df[(df[‘部门’] == ‘技术部’) & (df[‘薪资’] > 14000)]筛选出技术部且薪资高于14000的员工,逻辑非常清晰。
3. 数据清洗实战:把“脏数据”变成“干净数据”
真实世界的数据几乎没有完美的,缺失值、重复值、格式错乱是家常便饭。数据清洗可能占到你数据分析80%的时间,而Pandas提供了全套工具。
3.1 处理缺失值:别让NaN毁了你的分析
缺失值在Pandas里通常显示为 NaN(Not a Number)。首先,你得找到它们。df.isnull() 会返回一个布尔类型的DataFrame,告诉你每个位置是否是空值。df.isnull().sum() 则能快速统计每一列有多少个缺失值,这个命令我几乎每次打开新数据都会先跑一遍。
找到缺失值后,处理方式主要有两种:删除和填充。删除很简单,用 df.dropna()。但你要小心,dropna() 默认会删除任何包含NaN的行,这可能让你损失大量数据。你可以用 how=‘all’ 参数,只在整行都为空时才删除,或者用 subset=[‘列名’] 参数指定只检查某些列。
更常用的方法是填充。df.fillna() 是主力。最简单的就是用一个统一的值填充所有缺失值,比如 df.fillna(0)。但这样往往太粗暴。更合理的做法是:
- 用该列的均值填充数值列:
df[‘薪资’].fillna(df[‘薪资’].mean(), inplace=True) - 用该列的众数(出现最多的值)填充分类列:
df[‘部门’].fillna(df[‘部门’].mode()[0], inplace=True) - 用前一个或后一个有效值填充(对于时间序列数据特别有用):
df.fillna(method=‘ffill’)用前值填充。
3.2 处理重复数据:消灭“双胞胎”
重复数据会严重扭曲分析结果,比如让求和、平均值失真。df.duplicated() 可以帮你标记出哪些行是重复的(从第二行及以后的重复行标记为True)。而 df.drop_duplicates() 则直接删除重复行。
这里有个关键点:什么叫“重复”?默认情况下,Pandas会比较整行所有列的值。但有时我们只关心某几列不能重复。比如在一个用户表中,我们允许姓名重复,但“用户ID”必须唯一。这时可以用 subset 参数:df.drop_duplicates(subset=[‘用户ID’], keep=‘first’)。keep 参数决定保留哪一个,‘first’ 保留第一个出现的,‘last’ 保留最后一个,False 则删除所有重复项。
3.3 数据类型转换与格式规整
数据读进来后,类型经常不对。比如“日期”列可能是字符串,“价格”列可能混入了货币符号。df[‘列名’].astype(‘类型’) 是基础的类型转换方法。例如 df[‘年龄’] = df[‘年龄’].astype(‘int’)。
对于日期时间,我强烈推荐用 pd.to_datetime() 函数,它非常智能,能解析各种格式的日期字符串:
df[‘订单日期’] = pd.to_datetime(df[‘订单日期’], format=‘%Y/%m/%d’)
转换后,你就可以方便地提取年份、月份、星期几了:df[‘订单日期’].dt.year。
字符串列的处理也经常遇到。比如姓名大小写不一致,或者前后有空格。Pandas的字符串方法通过 .str 访问器调用,和Python原生的字符串方法很像:
df[‘姓名’].str.strip():去除首尾空格。df[‘姓名’].str.lower():全部转为小写。df[‘城市’].str.replace(‘市’, ‘’):替换字符。df[‘邮箱’].str.contains(‘@gmail.com’):检查是否包含特定字符,用于筛选。
4. 数据变形与合并:让数据为你所用
清洗干净的数据,往往还需要进行重塑、合并,才能满足分析的需求。这是Pandas另一个强大的领域。
4.1 数据排序与排名
排序是基本操作。df.sort_values(by=‘薪资’, ascending=False) 会按“薪资”列降序排列整个表格。如果你想按多列排序,比如先按“部门”升序,部门相同的再按“薪资”降序,可以这样:df.sort_values(by=[‘部门’, ‘薪资’], ascending=[True, False])。
除了排序,有时我们更需要知道数据的排名。df[‘薪资’].rank(method=‘dense’, ascending=False) 可以给出薪资的排名。method=‘dense’ 意味着并列名次不会占用后续名次(如1,2,2,3),而 method=‘min’ 则是常见的体育排名方式(如1,2,2,4)。
4.2 数据分组与聚合:洞察的源泉
“分组-聚合”是数据分析的核心范式。比如,我想知道每个部门的平均薪资和人数。用Pandas只需要一行代码:
df.groupby(‘部门’)[‘薪资’].agg([‘mean’, ‘count’])
groupby(‘部门’) 按部门分组,[‘薪资’] 指定我们对薪资列感兴趣,.agg() 是聚合函数,里面可以放多个,比如 ‘mean’(均值)、‘sum’(总和)、‘max’(最大值)、‘min’(最小值)、‘std’(标准差)等。
你还可以对不同的列应用不同的聚合函数:
df.groupby(‘部门’).agg({‘薪资’: ‘mean’, ‘年龄’: [‘max’, ‘min’]})
这行代码会生成一个表格,显示每个部门的平均薪资、最大年龄和最小年龄。分组聚合的结果通常是一个新的、索引为分组键的DataFrame,你可以用 .reset_index() 把分组键重新变成普通列,方便后续处理。
4.3 数据合并:连接多个数据表
数据很少只存在于一个表里。Pandas提供了多种合并方法,最常用的是 pd.merge(),它类似于SQL中的JOIN操作。
假设我们有两个表:df_orders(订单表,有user_id和amount)和 df_users(用户表,有user_id和city)。我们想把城市信息合并到订单表里:
df_merged = pd.merge(df_orders, df_users, on=‘user_id’, how=‘left’)
on=‘user_id’:指定根据哪个列进行连接。how=‘left’:指定连接方式为左连接,即以左边的订单表为主,保留所有订单,匹配不上用户信息的,城市列会显示为NaN。其他方式还有‘inner’(内连接,只保留两边都有的)、‘right’(右连接)、‘outer’(外连接,保留所有)。
另一种常用的合并是 pd.concat(),它用于沿着一个轴(行或列)堆叠多个DataFrame。比如你有1月、2月、3月三个结构相同的数据表,想合并成一个全年数据:df_year = pd.concat([df_jan, df_feb, df_mar], ignore_index=True)。ignore_index=True 会忽略原来的索引,生成新的连续索引。
5. 实战案例:分析一份电商销售数据
光说不练假把式。我们模拟一份简单的电商订单数据,把前面学的串起来用一遍。假设我们有一个 orders.csv 文件,包含以下字段:order_id, customer_id, product, quantity, unit_price, order_date。
import pandas as pd
import numpy as np
# 1. 读取数据
df = pd.read_csv(‘orders.csv’)
print(“数据概览:”)
print(df.head())
print(df.info()) # 查看数据类型和缺失情况
# 2. 数据清洗
# 检查缺失值
print(“\n缺失值统计:”)
print(df.isnull().sum())
# 假设发现unit_price有少量缺失,用该商品的平均单价填充
avg_price_by_product = df.groupby(‘product’)[‘unit_price’].transform(‘mean’)
df[‘unit_price’].fillna(avg_price_by_product, inplace=True)
# 处理日期
df[‘order_date’] = pd.to_datetime(df[‘order_date’])
# 3. 数据变形:创建新特征
# 计算每笔订单的销售额
df[‘revenue’] = df[‘quantity’] * df[‘unit_price’]
# 提取订单的月份
df[‘order_month’] = df[‘order_date’].dt.to_period(‘M’) # 格式如‘2024-01’
# 4. 数据分析
# 每月总销售额
monthly_revenue = df.groupby(‘order_month’)[‘revenue’].sum().reset_index()
print(“\n月度销售额:”)
print(monthly_revenue)
# 最畅销的商品Top 5
top_products = df.groupby(‘product’)[‘quantity’].sum().sort_values(ascending=False).head(5)
print(“\n最畅销商品Top 5:”)
print(top_products)
# 客户价值分析:计算每个客户的总消费额和订单数
customer_stats = df.groupby(‘customer_id’).agg(
total_spent=(‘revenue’, ‘sum’),
order_count=(‘order_id’, ‘nunique’) # 计算唯一订单数
).reset_index()
print(“\n客户价值分析(前10位):”)
print(customer_stats.sort_values(by=‘total_spent’, ascending=False).head(10))
# 5. 数据输出
# 将清洗和分析后的数据保存到新文件
df.to_csv(‘cleaned_orders.csv’, index=False)
customer_stats.to_csv(‘customer_analysis.csv’, index=False)
这个简单的流程覆盖了数据读取、清洗、特征工程、聚合分析和结果输出。你会发现,用Pandas写出的代码就像数据分析的“流水线”,逻辑清晰,易于维护和复查。真正用熟了之后,你处理数据的思路会完全改变,很多复杂的操作都能拆解成几个简单的Pandas步骤组合完成。我自己的经验是,遇到任何数据处理问题,先别急着写循环,想想能不能用 groupby、merge、apply 这些Pandas原语来解决,你会发现效率的提升是指数级的。
更多推荐
所有评论(0)