0%

Pandas数据炼金术

Pandas数据炼金术:从原始数据到商业洞察的完整工作流

在数据科学领域,我们常说“垃圾进,垃圾出”(Garbage In, Garbage Out)。无论你的机器学习模型多么精妙,可视化图表多么炫酷,如果输入的数据质量低下,那么得出的结论也必然是错误的。据估算,一个数据科学家或分析师80%的时间都花费在数据清洗和准备上。因此,掌握一套高效、系统的数据处理方法,远比学习几个花哨的算法模型更为重要。

Pandas,这个Python数据分析的基石库,正是为我们解决这一核心痛点而生的。它不仅仅是一个工具库,更是一套完整的数据处理哲学。本文将带你跳出零散的函数记忆,从一个完整工作流的视角,重新审视Pandas的强大能力。我们将模拟一个真实的数据分析场景,一步步完成从数据检查、清洗、预处理,到提取、筛选、汇总、统计,最终输出成果的全过程。

第一阶段:数据诊断与检查

想象一下,你是一名医生,面前躺着一位病人(你的数据集)。在开药方(进行分析)之前,你必须先进行全面的体检,了解病人的基本状况和潜在问题。Pandas为我们提供了快速“诊断”数据的强大工具。

数据加载与概览

一切从加载数据开始。Pandas可以轻松读取CSV、Excel、SQL数据库等多种格式的数据。加载后,我们不应该一头扎进数据细节,而是要先看“全貌”。

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd

# 模拟加载一份销售数据
df = pd.read_csv('sales_data.csv')

# .head() 查看前5行,快速了解数据结构
print(df.head())

# .tail() 查看后5行,检查数据末尾是否有异常
print(df.tail())

# .sample(5) 随机查看5行,避免数据排序带来的偏差
print(df.sample(5))

深度体检:info()与describe()

如果说head()是看一眼病人的气色,那么info()describe()就是一份详细的体检报告。

df.info()方法会告诉你数据集中有多少行、多少列,每一列的数据类型是什么(是整数、浮点数还是对象字符串),以及最关键的非空值数量。通过这个报告,你可以立刻发现哪些列存在大量的缺失值,哪些列的数据类型是错误的(例如,日期被读成了字符串)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
print(df.info())

<class 'pandas.DataFrame'>
RangeIndex: 11 entries, 0 to 10
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 OrderID 11 non-null int64
1 Name 11 non-null str
2 Age 9 non-null float64
3 City 11 non-null str
4 OrderDate 11 non-null str
5 Category 11 non-null str
6 Quantity 11 non-null int64
7 UnitPrice 11 non-null float64
dtypes: float64(2), int64(2), str(4)
memory usage: 836.0 bytes
None

df.describe()方法则专注于数值型列,它会为你生成一份统计摘要,包括计数、均值、标准差、最小值、最大值以及四分位数。这能让你迅速了解数据的分布情况,比如价格是否在合理范围内,是否存在一些极端值。

1
2
3
4
5
6
7
8
9
10
11
12
print(df.describe())


OrderID Age Quantity UnitPrice
count 11.000000 9.000000 11.000000 11.000000
mean 1005.181818 48.777778 2.181818 48.999091
std 3.060006 56.949051 1.328020 49.314521
min 1001.000000 22.000000 1.000000 -10.000000
25% 1002.500000 28.000000 1.000000 17.750000
50% 1005.000000 30.000000 2.000000 30.000000
75% 1007.500000 35.000000 2.500000 65.000000
max 1010.000000 200.000000 5.000000 150.000000

第二阶段:数据清洗与预处理

体检完毕,问题浮出水面:缺失值、重复项、异常值、格式混乱。现在,是时候动手“治疗”了。

处理缺失值:填还是删?

缺失值是数据集中最常见的问题。处理它们没有标准答案,完全取决于业务场景。

  • 删除:如果某一行或某一列的缺失值比例极高(例如超过80%),那么它包含的信息量就很少,可以直接使用df.dropna()将其删除。
  • 填充:对于关键的数值列,我们更倾向于填充。可以用均值、中位数或众数来填充。例如,用所有用户的平均年龄来填充缺失的年龄。Pandas的df.fillna(value)方法可以优雅地完成这项工作。对于时间序列数据,还可以使用前向填充(ffill)或后向填充(bfill),即用前一个或后一个时间点的值来填充。

处理重复值:去伪存真

重复的数据会扭曲我们的统计结果,尤其是在计算总和或计数时。使用df.duplicated()可以检查哪些行是重复的,而df.drop_duplicates()则可以一键删除所有重复项,只保留唯一记录。

处理异常值:识别并剔除噪音

异常值,或称离群点,是那些远离数据主体分布的极端值。例如,在一个成年人的数据集中出现一个年龄为200岁的记录。这些值会严重影响均值和标准差的计算。一种常用的检测方法是IQR(四分位距)法。通过计算第一四分位数(Q1)和第三四分位数(Q3),我们可以定义一个合理的范围(Q1 - 1.5 IQR, Q3 + 1.5 IQR),落在这个范围之外的数据点就可以被视为异常值并进行过滤。

数据类型转换:统一语言

确保数据类型的正确性是后续分析的基础。Pandas提供了强大的转换功能。pd.to_datetime()可以将字符串格式的日期(如”2023-01-01”)转换为Pandas的Timestamp对象,从而可以进行日期运算。astype()方法则可以将一列数据强制转换为指定类型,例如将浮点数转换为整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import pandas as pd
import numpy as np

# 1. 加载数据 (假设你已经保存了 sales_data.csv)
df = pd.read_csv('sales_data.csv')

print("=== 原始数据概览 ===")
print(df)
print(df.info())

# ==========================================
# 2. 数据清洗实战
# ==========================================

# --- A. 处理重复值 ---
# 检查重复行数量
print(f"\n[检查] 删除前的重复行数: {df.duplicated().sum()}")

# 删除完全重复的行,保留第一条
df = df.drop_duplicates()
print(f"[操作] 已删除重复行,当前剩余行数: {len(df)}")

# --- B. 处理缺失值 ---
# 检查缺失情况
print(f"\n[检查] 年龄列缺失值数量: {df['Age'].isnull().sum()}")

# 策略:用年龄的中位数填充,避免均值受异常值影响
# 注意:此时还没处理异常值,所以用中位数更稳健
median_age = df['Age'].median()
df['Age'] = df['Age'].fillna(median_age)
print(f"[操作] 年龄列缺失值已用中位数 {median_age} 填充")

# --- C. 处理异常值 ---
# 1. 逻辑异常:单价不能为负数
# 将负数单价替换为 NaN,然后再用均值填充,或者直接删除该行
# 这里演示替换为均值
# mean()是求均值
mean_price = df[df['UnitPrice'] > 0]['UnitPrice'].mean()
df.loc[df['UnitPrice'] < 0, 'UnitPrice'] = mean_price
print(f"[操作] 发现负数单价,已替换为均值 {mean_price:.2f}")

# 2. 数值离群点:年龄为200岁
# 使用 IQR (四分位距) 法剔除
Q1 = df['Age'].quantile(0.25)
Q3 = df['Age'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 筛选出正常年龄的数据
original_len = len(df)
df = df[(df['Age'] >= lower_bound) & (df['Age'] <= upper_bound)]
print(f"[操作] 使用IQR法剔除年龄异常值,共移除 {original_len - len(df)} 行")

# --- D. 数据类型转换 ---
# 将订单日期转换为 datetime 对象
df['OrderDate'] = pd.to_datetime(df['OrderDate'])
print(f"\n[操作] 订单日期列类型已转换为: {df['OrderDate'].dtype}")

# ==========================================
# 3. 清洗后的结果验证
# ==========================================
print("\n=== 清洗后的数据预览 ===")
print(df)
print("\n=== 清洗后的数据信息 ===")
print(df.info())

第三阶段:数据提取、筛选与特征工程

数据变得干净后,我们就可以从中提取有价值的信息了。这个过程就像从矿石中提炼金属,需要精准的筛选和巧妙的加工。

精准筛选:布尔索引与query()

Pandas的布尔索引功能极其强大,它允许你使用类似SQL的WHERE子句的逻辑来筛选数据。例如,df[(df['age'] > 30) & (df['city'] == 'Beijing')]可以筛选出所有年龄大于30且城市为北京的用户。对于更复杂的条件,df.query()方法提供了一种更简洁、可读性更高的方式,例如df.query('age > 30 and city == "Beijing"')

特征工程:创造新价值

特征工程是数据预处理中最具创造性的环节。它的目的是从现有数据中构造出对模型更有用的新特征。

  • 字符串操作:Pandas的str访问器可以让你轻松处理文本数据。例如,df['name'].str.lower()可以将所有名字转为小写,df['email'].str.contains('@gmail')可以筛选出使用Gmail邮箱的用户。
  • 日期时间特征:对于时间序列数据,dt访问器是你的得力助手。你可以轻松提取出年、月、日、星期几、季度等信息。例如,df['order_date'].dt.month可以提取出订单的月份,这对于分析季节性销售趋势至关重要。
  • 自定义特征:通过apply()函数,你可以对每一行或每一列应用自定义的逻辑。例如,你可以根据“出生年月”列计算出“年龄”列,或者根据“销售额”和“成本”列计算出“利润”列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59


print("=== 当前数据预览(清洗后) ===")
print(df[['Name', 'City', 'OrderDate', 'Category', 'Quantity', 'UnitPrice']])

# ==========================================
# 1. 数据筛选
# ==========================================
print("\n--- 1. 数据筛选 ---")

# --- A. 单条件筛选:找出所有电子产品 ---
electronics_df = df[df['Category'] == 'Electronics']
print(f"\n[筛选] 电子产品订单数量: {len(electronics_df)}")

# --- B. 多条件筛选:找出“纽约”且“年龄大于25岁”的客户 ---
# 注意:Pandas中多条件要用 & 符号,且每个条件要加括号
target_customers = df[(df['City'] == 'New York') & (df['Age'] > 25)]
print(f"[筛选] 纽约年龄>25的客户:\n{target_customers[['Name', 'City', 'Age']]}")

# --- C. 使用 query() 方法(更优雅的写法) ---
# 功能同上,但语法更像自然语言
target_customers_query = df.query("City == 'New York' and Age > 25")
print(f"[筛选] 使用 query 方法筛选结果: \n{target_customers_query[['Name', 'City', 'Age']]}")

# ==========================================
# 2. 特征工程
# ==========================================
print("\n--- 2. 特征工程 ---")

# --- A. 基于现有列计算新列:计算“总销售额” ---
# 公式:总销售额 = 单价 * 数量
df['TotalRevenue'] = df['UnitPrice'] * df['Quantity']
print("[特征] 已创建 'TotalRevenue' 列 (单价 * 数量)")

# --- B. 日期特征提取:提取“月份”和“星期” ---
# 利用 .dt 访问器
df['OrderMonth'] = df['OrderDate'].dt.month # 提取月份 (1, 2, 3...)
df['OrderWeekDay'] = df['OrderDate'].dt.day_name() # 提取星期几 (Monday, Tuesday...)
print("[特征] 已提取 'OrderMonth' 和 'OrderWeekDay' 列")

# --- C. 文本特征处理:城市名称标准化 ---
# 假设数据中城市名称大小写不统一,我们统一转为大写
df['City_Clean'] = df['City'].str.upper()
print("[特征] 已创建标准化城市名 'City_Clean'")

# --- D. 分箱操作:将年龄划分为不同年龄段 ---
# 将连续的年龄数值转换为分类标签
bins = [0, 25, 35, 100]
labels = ['青年', '中青年', '中老年']
df['AgeGroup'] = pd.cut(df['Age'], bins=bins, labels=labels)
print("[特征] 已完成年龄分箱 'AgeGroup'")

# ==========================================
# 3. 最终结果展示
# ==========================================
print("\n=== 最终加工完成的数据 ===")
# 只展示我们感兴趣的列
columns_to_show = ['Name', 'Age', 'AgeGroup', 'City', 'City_Clean', 'OrderWeekDay', 'TotalRevenue']
print(df[columns_to_show])

第四阶段:数据汇总、统计与洞察

当数据被清洗和加工完毕后,我们就可以开始真正的分析了。这一步的目标是将成千上万行的明细数据,浓缩成简洁有力的统计指标和报表。

分组聚合:groupby()的威力

groupby()是Pandas中最强大的功能之一,它遵循“拆分-应用-合并”的模式。你可以按照一个或多个列对数据进行分组,然后对每个组应用聚合函数(如求和、均值、计数等)。例如,df.groupby('city')['sales'].sum()可以快速计算出每个城市的总销售额。你还可以使用agg()函数对不同的列应用不同的聚合函数,例如df.groupby('category').agg({'sales': 'sum', 'profit': 'mean'}),一次性得到每个类别的销售总额和平均利润。

数据透视表:多维分析的利器

如果你熟悉Excel,那么pivot_table()对你来说会非常亲切。它是进行多维数据分析的绝佳工具。通过指定行、列和值,你可以轻松创建一个交叉报表。例如,pd.pivot_table(df, values='sales', index='city', columns='product_category', aggfunc='sum')可以生成一个以城市为行、产品类别为列、销售额为值的二维表格,让你一目了然地看到不同城市各类产品的销售情况。

描述性统计:深入理解数据

除了describe()提供的基础统计量,Pandas还提供了计算相关系数(corr())、协方差(cov())等高级统计功能。通过计算变量之间的相关系数矩阵,你可以发现哪些因素之间存在关联,为后续的建模提供方向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# ==========================================
# 1. 分组聚合 (groupby)
# ==========================================
print("\n--- 1. 分组聚合 ---")

# --- A. 单维度分组:按“产品类别”统计 ---
# 计算每个类别的总销售额和平均订单金额
category_stats = df.groupby('Category')['TotalRevenue'].agg(['sum', 'mean']).round(2)
category_stats.columns = ['总销售额', '平均订单额']
print(f"\n[聚合] 各产品类别销售表现:\n{category_stats}")

# --- B. 多维度分组:按“城市”和“年龄段”统计 ---
# 查看不同城市、不同年龄段的订单数量
city_age_group = df.groupby(['City', 'AgeGroup']).size().unstack(fill_value=0)
print(f"\n[聚合] 各城市不同年龄段客户订单数:\n{city_age_group}")

# ==========================================
# 2. 数据透视表 (pivot_table)
# ==========================================
print("\n--- 2. 数据透视表 ---")

# --- 创建一个交叉报表 ---
# 行:城市
# 列:产品类别
# 值:总销售额
# 聚合方式:求和,缺失值填充为0
pivot = pd.pivot_table(
df,
values='TotalRevenue',
index='City',
columns='Category',
aggfunc='sum',
fill_value=0
)
print(f"[透视表] 各城市-各品类销售额矩阵:\n{pivot}")

# ==========================================
# 3. 统计分析与洞察
# ==========================================
print("\n--- 3. 统计分析与洞察 ---")

# --- A. 描述性统计 ---
# 快速了解销售额的整体分布情况
print(f"\n[统计] 销售额描述性统计:\n{df['TotalRevenue'].describe().round(2)}")

# --- B. 相关性分析 ---
# 虽然本例数据简单,但可以演示如何计算
# 例如,我们可以看看“订单金额”和“客户年龄”(用AgeGroup的编码代替)是否有关系
# (这里仅为演示,实际业务中需要更严谨的设计)
correlation = df['TotalRevenue'].corr(df['Age'])
print(f"\n[洞察] 订单金额与年龄组别的相关系数: {correlation:.2f}")
print(f" (相关系数接近0表示无明显线性关系,接近1或-1表示强相关)")

# --- C. 寻找“明星”与“问题” ---
# 找出平均订单额最高的城市
best_city = df.groupby('City')['TotalRevenue'].mean().idxmax()
print(f"\n[洞察] 平均订单额最高的城市是: {best_city}")

# 找出总销售额最低的产品类别
worst_category = df.groupby('Category')['TotalRevenue'].sum().idxmin()
print(f"[洞察] 总销售额最低的产品类别是: {worst_category}")

第五阶段:数据输出与可视化

分析的最终目的是交付洞察。将你的发现以清晰、直观的方式呈现出来,是整个工作流的最后一步,也是至关重要的一步。

导出结果

Pandas支持将处理后的DataFrame导出为多种格式。to_csv()to_excel()是最常用的方法。在导出Excel文件时,你可以通过index=False参数去掉恼人的索引列,让报表更加整洁。对于需要存入数据库的数据,to_sql()方法可以让你直接将DataFrame写入SQL数据库中,实现数据的持久化。

数据可视化

虽然Pandas不是专业的可视化库,但它与Matplotlib的集成非常好,可以进行快速绘图。df.plot()方法可以方便地绘制折线图、柱状图、散点图等。例如,df.groupby('month')['sales'].sum().plot(kind='bar')可以快速生成一张月度销售额的柱状图,让你的分析结论更加直观和有说服力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# 模拟一份汇总后的数据(接上一阶段的结果)
data = {
'Category': ['Electronics', 'Books', 'Clothing', 'Home'],
'TotalRevenue': [505.50, 95.95, 135.00, 75.00],
'AvgOrderValue': [101.10, 31.98, 67.50, 75.00]
}
summary_df = pd.DataFrame(data)
summary_df.set_index('Category', inplace=True) # 将类别设为索引

print("=== 准备可视化的数据 ===")
print(summary_df)

# ==========================================
# 1. 数据输出 (保存结果)
# ==========================================
print("\n--- 1. 数据输出 ---")

# --- A. 导出为 CSV (最通用) ---
# index=True 表示把索引(也就是 Category 列)也写进去
summary_df.to_csv('sales_report.csv', encoding='utf-8-sig')
print("[保存] 已保存为 sales_report.csv")

# --- B. 导出为 Excel (更美观) ---
# 注意:需要安装 openpyxl 库 (pip install openpyxl)
try:
summary_df.to_excel('sales_report.xlsx', sheet_name='SalesSummary')
print("[保存] 已保存为 sales_report.xlsx")
except ImportError:
print("[提示] 未安装 openpyxl,跳过 Excel 导出 (请运行: pip install openpyxl)")

# ==========================================
# 2. 数据可视化 (绘图)
# ==========================================
print("\n--- 2. 数据可视化 ---")

# --- 设置绘图风格 ---
# 设置中文字体,防止中文乱码 (Windows通常用SimHei,Mac用Arial Unicode MS)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
plt.style.use('seaborn-v0_8') # 使用 seaborn 风格,更好看

# --- A. 柱状图:比较各类别总销售额 ---
plt.figure(figsize=(10, 6)) # 设置画布大小
# Pandas 直接绘图
summary_df['TotalRevenue'].plot(kind='bar', color='skyblue', edgecolor='black')

plt.title('各类别总销售额对比', fontsize=16) # 标题
plt.xlabel('产品类别', fontsize=12) # X轴标签
plt.ylabel('总销售额 (元)', fontsize=12) # Y轴标签
plt.xticks(rotation=0) # X轴标签不旋转
plt.grid(axis='y', linestyle='--', alpha=0.5) # 显示Y轴网格线

# 在柱子上显示具体数值
for i, v in enumerate(summary_df['TotalRevenue']):
plt.text(i, v + 1, f"{v:.1f}", ha='center', fontsize=10)

plt.tight_layout()
plt.show()

# --- B. 折线图:查看趋势 (假设这是时间序列数据) ---
# 虽然这里是类别数据,但演示一下折线图画法
plt.figure(figsize=(10, 6))
summary_df['AvgOrderValue'].plot(kind='line', marker='o', color='red', linewidth=2)

plt.title('各类别平均客单价趋势', fontsize=16)
plt.xlabel('产品类别', fontsize=12)
plt.ylabel('平均客单价 (元)', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()

# --- C. 饼图:查看占比 ---
plt.figure(figsize=(8, 8))
plt.pie(summary_df['TotalRevenue'],
labels=summary_df.index,
autopct='%1.1f%%', # 显示百分比格式
startangle=140, # 起始角度
shadow=True) # 添加阴影

plt.title('各类别销售额占比', fontsize=16)
plt.axis('equal') # 保证饼图是正圆
plt.tight_layout()
plt.show()

从混乱到有序,从原始到精炼,Pandas为我们提供了一条清晰而强大的数据处理路径。掌握这条路径上的每一个环节,你就能真正驾驭数据,从中挖掘出宝贵的商业价值。

欢迎关注我的其它发布渠道