嘿,朋友,欢迎来到数据的世界!如果你正对着满屏的杂乱数据发愁,或者觉得 Pandas 只是个处理 Excel 的工具,那这篇文章就是为你准备的。我会把你从“只会 import pandas as pd”的小白,带到能独立清洗脏数据、画出惊艳图表的分析高手。我们不只讲语法,更讲那些实战中踩过的坑和解决思路。
为什么要学 Pandas?它到底强在哪?
首先,咱们得聊聊为什么 Pandas 是数据分析界的“瑞士军刀”。
想象一下,你手头有一个 500MB 的 CSV 文件,里面有100万条用户交易记录。你用 Excel 打开?恭喜你,电脑可能已经卡死,或者打开后只能看到前100万行(Excel 上限就是104万行)。而且,Excel 公式处理这种量级的数据,速度会让你等到天荒地老。
Pandas 不一样。它是基于 Python 的开源库,专为高效处理结构化数据而生。它的核心是两个数据结构:
- Series:一维数组,像一列数据,带标签。
- DataFrame:二维表格,像 Excel 里的 sheet,有行有列,标签灵活。
更重要的是,Pandas 背后是 NumPy,计算速度飞快。你写几行代码,就能完成以前写几十行循环才能搞定的事。
第一步:让数据“活”起来——加载与初步探索
在清洗之前,你得先认识你的数据。就像见朋友之前,先看看对方长什么样、性格如何。
1.1 加载数据:不止是 read_csv
很多人第一步就是 df = pd.read_csv('data.csv')。没错,但这只是基础。实战中,你的数据可能藏在各种地方。
import pandas as pd
import numpy as np
# 1. 加载 CSV,指定编码,避免中文乱码
df = pd.read_csv('user_transactions.csv', encoding='utf-8')
# 2. 加载 Excel 文件
df_excel = pd.read_excel('report.xlsx', sheet_name='Sheet1')
# 3. 从数据库直接读取(以 SQLite 为例)
import sqlite3
conn = sqlite3.connect('my_database.db')
df_db = pd.read_sql_query("SELECT * FROM users", conn)
conn.close()
# 4. 加载 JSON 数据
df_json = pd.read_json('data.json')
小技巧:如果文件很大,用 nrows=1000 先加载一部分看看结构,或者用 chunksize=10000 分块读取,避免内存爆炸。
1.2 快速窥探:head, tail, info, describe
拿到数据后,别急着动手洗,先“打量”一番。
# 看前5行和后5行,快速了解数据结构
print(df.head())
print(df.tail())
# 查看数据基本信息:行数、列数、非空值、数据类型
print(df.info())
# 对数值列进行统计描述:均值、标准差、最小值、最大值等
print(df.describe())
# 查看所有列名
print(df.columns)
info() 的输出特别有用。比如,你会看到:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 user_id 10000 non-null int64
1 name 9950 non-null object
2 age 9800 non-null float64
3 signup_date 10000 non-null datetime64[ns]
4 amount 10000 non-null float64
dtypes: datetime64[ns](1), float64(2), int64(1), object(1)
memory usage: 390.7+ KB
从这里你立刻知道:name 列有50个缺失值,age 列有200个缺失值。数据清洗的靶子就出来了。
describe() 则告诉你数值列的分布情况,比如 amount 的均值、最大值、最小值,有没有异常值(比如最大金额是999999,明显是录入错误)。
第二步:给数据“洗澡”——清洗与预处理
脏数据是分析的大敌。清洗,就是把数据从“能用但坑多”变成“干净且可靠”。
2.1 缺失值处理:丢、补、还是标?
缺失值(NaN/None)怎么处理,没有标准答案,要看场景。
方法一:直接删除 如果缺失比例很小(比如%),且数据量充足,直接删除最省事。
# 删除包含任何缺失值的行
df_clean = df.dropna()
# 删除指定列缺失的行
df_clean = df.dropna(subset=['age', 'amount'])
方法二:填充值 如果数据量小,或者缺失很关键,就得填充。
- 数值型:用均值、中位数、众数填充。中位数对异常值不敏感,更稳健。
- 分类数据:用众数填充,或者填“未知”。
- 时间序列:向前填充(ffill)或向后填充(bfill)。
- 自定义:填0,或填一个特殊值(如-1)。
# 用中位数填充 age
df['age'].fillna(df['age'].median(), inplace=True)
# 用众数填充 name 的缺失值(先找众数)
mode_name = df['name'].mode()[0]
df['name'].fillna(mode_name, inplace=True)
# 向前填充(时间序列常用)
df['amount'].fillna(method='ffill', inplace=True)
方法三:标记为缺失 有时候,缺失本身就有信息!比如,用户没填手机号,可能代表他不希望被联系。这时,与其填充,不如新建一列标记“是否缺失”。
df['is_age_missing'] = df['age'].isnull().astype(int)
2.2 重复值处理:独一无二
重复数据会污染统计结果。duplicated() 和 drop_duplicates() 是利器。
# 检查有多少重复行
print(df.duplicated().sum())
# 删除重复行,保留第一次出现的
df_no_dup = df.drop_duplicates()
# 或者保留最后一次出现的
df_no_dup = df.drop_duplicates(keep='last')
# 针对特定列去重
df_no_dup = df.drop_duplicates(subset=['user_id'])
2.3 数据类型转换:让数据“说得对”
Pandas 有时会自动推断类型,但往往猜错。比如日期列被读成了字符串,数字列被读成了字符串(因为混了空值)。
# 将字符串日期转为 datetime
df['signup_date'] = pd.to_datetime(df['signup_date'], errors='coerce')
# errors='coerce' 会把无法解析的值变成 NaT(Not a Time),而不是报错
# 将分类数据转为 categorical 类型,节省内存
df['gender'] = df['gender'].astype('category')
# 将字符串金额转为数值,去掉逗号
df['amount'] = df['amount'].str.replace(',', '').astype(float)
2.4 异常值检测与处理:揪出“害群之马”
异常值可能是录入错误,也可能是真实但极端的情况。处理前一定要先分析原因。
方法一:3σ 原则(适用于正态分布) 假设数据服从正态分布,值落在均值±3个标准差之外的,视为异常。
mean = df['age'].mean()
std = df['age'].std()
df['age_clean'] = df['age'].clip(lower=mean-3*std, upper=mean+3*std)
# clip 会把超出范围的值截断到边界值
方法二:IQR(四分位距)法(更稳健) 不假设分布,用四分位数。
Q1 = df['amount'].quantile(0.25)
Q3 = df['amount'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 剔除异常值
df_clean = df[(df['amount'] >= lower_bound) & (df['amount'] <= upper_bound)]
方法三:业务规则剔除 比如,年龄不可能超过150岁,订单金额不可能为负。直接用逻辑过滤。
df = df[(df['age'] >= 0) & (df['age'] <= 120)]
df = df[df['amount'] > 0]
2.5 数据标准化与归一化:让它们站在同一起跑线
当特征量纲不同时(比如年龄0-100,收入0-100万),模型会偏向大数值特征。标准化(Z-score)和归一化(Min-Max)是常用手段。
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 标准化:均值为0,标准差为1
scaler = StandardScaler()
df[['age_scaled']] = scaler.fit_transform(df[['age']])
# 归一化:缩放到[0,1]区间
scaler_minmax = MinMaxScaler()
df[['amount_normalized']] = scaler_minmax.fit_transform(df[['amount']])
注意:通常只在建模前做,探索性分析时保留原始值更直观。
第三步:让数据“说话”——清洗后的探索性分析(EDA)
清洗完数据,别急着建模,先画几眼看看。EDA(Exploratory Data Analysis)能帮你发现规律、验证假设。
3.1 单变量分析:看分布
import matplotlib.pyplot as plt
import seaborn as sns
# 设置中文字体(避免乱码)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
# 数值分布直方图
plt.figure(figsize=(10, 6))
sns.histplot(df['age'], kde=True, bins=30)
plt.title('Age Distribution')
plt.show()
# 分类数据频次条形图
plt.figure(figsize=(10, 6))
sns.countplot(data=df, x='gender')
plt.title('Gender Counts')
plt.show()
3.2 双变量分析:看关系
# 散点图:年龄 vs 金额
plt.figure(figsize=(10, 6))
sns.scatterplot(data=df, x='age', y='amount', hue='gender')
plt.title('Age vs Amount by Gender')
plt.show()
# 箱线图:不同性别的金额分布
plt.figure(figsize=(10, 6))
sns.boxplot(data=df, x='gender', y='amount')
plt.title('Amount Distribution by Gender')
plt.show()
# 热力图:相关系数矩阵
plt.figure(figsize=(8, 6))
corr = df[['age', 'amount']].corr()
sns.heatmap(corr, annot=True, cmap='coolwarm')
plt.title('Correlation Heatmap')
plt.show()
第四步:数据变型与分组聚合——透视数据
清洗后的数据,往往需要变形才能用于分析或建模。
4.1 重塑数据:长格式 vs 宽格式
# 假设你有一个宽格式数据:每个人一行,多个时间点的数据在列中
df_wide = pd.DataFrame({
'user_id': [1, 2],
'score_2023': [90, 85],
'score_2024': [92, 88]
})
# 转为长格式:每行一个时间点
df_long = df_wide.melt(id_vars='user_id', var_name='year', value_name='score')
# 结果:
# user_id year score
# 0 1 score_2023 90
# 1 2 score_2023 85
# 2 1 score_2024 92
# 3 2 score_2024 88
4.2 分组聚合:GroupBy 的魔力
这是 Pandas 最强大的功能之一。模仿 SQL 的 GROUP BY。
# 按性别和年龄段(假设已分箱)统计平均金额
df['age_group'] = pd.cut(df['age'], bins=[0, 18, 35, 60, 100], labels=['Young', 'Adult', 'Middle', 'Senior'])
result = df.groupby(['gender', 'age_group'])['amount'].agg(['mean', 'std', 'count']).reset_index()
print(result)
4.3 合并数据:Merge 与 Join
来自不同表的数据,经常需要合并。
# 内连接(默认)
merged = pd.merge(df_users, df_orders, on='user_id', how='inner')
# 左连接:保留所有用户,订单为空则填NaN
merged = pd.merge(df_users, df_orders, on='user_id', how='left')
# 按列名合并不同键
merged = pd.merge(df1, df2, left_on='key1', right_on='key2', how='outer')
第五步:可视化进阶——用图表讲一个完整的故事
前面提到了一些基础图表,下面来看看如何让可视化更专业、更具洞察力。
5.1 多子图布局:一张图说清楚多件事
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 子图1:年龄分布
sns.histplot(df['age'], kde=True, bins=30, ax=axes[0, 0])
axes[0, 0].set_title('Age Distribution')
# 子图2:金额分布(对数刻度,因为通常偏态)
sns.histplot(df['amount'], kde=True, bins=30, ax=axes[0, 1])
axes[0, 1].set_title('Amount Distribution')
axes[0, 1].set_xscale('log')
# 子图3:性别与年龄的关系
sns.boxplot(data=df, x='gender', y='age', ax=axes[1, 0])
axes[1, 0].set_title('Age by Gender')
# 子图4:时间趋势(假设有一个日期列)
df['month'] = df['signup_date'].dt.to_period('M')
monthly_signups = df['month'].value_counts().sort_index()
monthly_signups.plot(kind='line', marker='o', ax=axes[1, 1])
axes[1, 1].set_title('Monthly Signups')
plt.tight_layout()
plt.show()
5.2 交互式可视化:Plotly 让你动起来
静态图有时不够。Plotly 能生成可交互的网页图表,鼠标悬停看数值,缩放平移。
import plotly.express as px
# 散点图,可交互
fig = px.scatter(df, x='age', y='amount', color='gender', size='amount', hover_data=['name'])
fig.update_layout(title='Age vs Amount')
fig.show()
# 时间序列图
fig = px.line(df, x='signup_date', y='amount', title='Amount over Time')
fig.show()
5.3 专业图表:Seaborn 的高级功能
# 成对关系图:快速看所有数值列两两关系
pair_plot = sns.pairplot(df[['age', 'amount', 'score']])
pair_plot.fig.suptitle('Pairwise Relationships', y=1.02)
plt.show()
# 联合分布图:同时看两个变量的分布和相关性
sns.jointplot(data=df, x='age', y='amount', kind='scatter', hue='gender')
plt.show()
# 小提琴图:比箱线图更多信息
sns.violinplot(data=df, x='gender', y='amount')
plt.show()
实战案例:端到端分析流程
光说不练假把式。让我们用一个完整的小项目,把以上技能串起来。
项目目标:分析电商用户行为数据,找出高价值用户特征,并可视化展示。
步骤1:加载与初步检查
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 加载数据
df = pd.read_csv('ecommerce_data.csv')
print(df.shape)
print(df.info())
print(df.describe())
步骤2:清洗 “`python
1. 处理缺失值
print(df.isnull().sum()) df[‘email’] = df[‘email’].fillna(‘unknown’) df[‘age’] = df[‘age’].fillna(df[‘age’].median()) df[‘last_purchase_date’] = df[‘last_purchase_date’].fillna(df[‘last_purchase_date’].mode()[0])
2. 处理重复值
df = df.drop_duplicates(subset=[‘user_id’])
3. 类型转换
df[‘last_purchase_date’] = pd.to_datetime(df[‘last_purchase_date’]) df[‘age’] = df[‘age’].astype(int)
4. 异常值处理(IQR法处理purchase_amount)
Q1 = df[‘purchase_amount’].quantile(0.25) Q3 = df[‘purchase_amount’].quantile(0.75) IQR = Q3 - Q1 lower = Q1 - 1.5 * IQR upper = Q3 + 1.5 *
