说到Python数据分析,很多人第一反应就是“这三兄弟:NumPy、Pandas、Matplotlib”。听着耳熟吧?但真正上手后,很多人卡在“知道概念,不会用”或者“代码跑通了,但看不懂数据在说什么”。今天咱们不聊虚的,直接深入实战,用大白话把这三个库的核心技巧掰开揉碎讲清楚,顺便给你几个真实的代码场景,让你看完就能用。
先聊聊NumPy:数据处理的“底层肌肉”
很多人觉得NumPy太基础,跳过它直接上Pandas。这是个误区。NumPy是Pandas的基石,不理解它,处理大数据时你会频繁遇到性能瓶颈。
核心思想:向量化操作
传统Python列表处理数据,比如对每个元素加1,你得写循环:
import numpy as np
# 假设你有一组销售数据
sales = [100, 150, 200, 250, 300]
# 传统方法:循环
adjusted_sales = []
for s in sales:
adjusted_sales.append(s * 1.1) # 加10%税
print(adjusted_sales) # [110.0, 165.0, 220.0, 275.0, 330.0]
用NumPy,一行搞定,而且快得多:
import numpy as np
sales_np = np.array([100, 150, 200, 250, 300])
# 向量化操作:直接对整个数组运算
adjusted_sales_np = sales_np * 1.1
print(adjusted_sales_np) # [110. 165. 220. 275. 330.]
为什么快? NumPy在底层用C语言实现,连续内存存储,避免了Python对象开销。当你处理百万级数据时,这个差距是数量级的。
实战技巧:广播机制(Broadcasting)
这是NumPy最优雅的特性之一。你可以对不同形状的数组进行运算,NumPy会自动“广播”较小的数组。
import numpy as np
# 假设你有3个产品的价格
prices = np.array([10, 20, 30])
# 假设你有4个客户的购买数量
quantities = np.array([2, 3, 1, 4]).reshape(4, 1) # 变成4行1列
# 计算每个客户买每个产品的总价
total = prices * quantities
print(total)
# 输出:
# [[20 40 60]
# [30 60 90]
# [10 20 30]
# [40 80 120]]
看,prices只有3个元素,quantities是4x1,但NumPy自动把prices广播成4x3,每个客户乘以每个价格。这在实际数据分析中超级常用,比如计算不同地区的平均气温偏差。
常见问题:shape和dtype
NumPy数组有shape(维度)和dtype(数据类型)两个关键属性。理解它们能避免90%的错误。
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr.shape) # (2, 3) - 2行3列
print(arr.dtype) # int64
# 强制改变数据类型(注意:这会复制数据!)
float_arr = arr.astype(float)
print(float_arr.dtype) # float64
提示:在处理大规模数据时,选择合适的dtype能节省大量内存。比如,如果数据都是0-255的整数(如图像像素),用uint8比默认int64节省8倍内存。
Pandas:数据分析的“瑞士军刀”
如果说NumPy是底层肌肉,Pandas就是灵活的手脚。它让数据清洗、转换、分析变得直观。
数据结构:Series和DataFrame
Series是一维标签数组,DataFrame是二维表格(类似Excel)。
import pandas as pd
# 创建Series
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
print(s)
# a 10
# b 20
# c 30
# d 40
# dtype: int64
# 创建DataFrame
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五'],
'年龄': [25, 30, 35],
'城市': ['北京', '上海', '广州']
})
print(df)
# 姓名 年龄 城市
# 0 张三 25 北京
# 1 李四 30 上海
# 2 王五 35 广州
数据读取与探索
实际工作中,80%的时间在数据处理上。Pandas能轻松读取各种格式:
import pandas as pd
# 读取CSV
df = pd.read_csv('sales_data.csv')
# 读取Excel
df_excel = pd.read_excel('report.xlsx')
# 快速查看数据
print(df.head()) # 前5行
print(df.tail()) # 后5行
print(df.shape) # (行数, 列数)
print(df.info()) # 列信息、非空值计数、数据类型
print(df.describe()) # 数值列的统计摘要
数据清洗:缺失值处理
缺失值是真实数据的常态。Pandas提供了多种处理方式:
import pandas as pd
import numpy as np
# 模拟有缺失值的数据
df = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, np.nan, 8],
'C': ['x', 'y', 'z', 'w']
})
# 方法1:删除缺失值
df_cleaned = df.dropna()
# 方法2:填充缺失值
df_filled = df.fillna(0) # 用0填充
df_filled = df.fillna(method='ffill') # 用前一个值填充(时间序列常用)
# 方法3:插值(更智能)
df_interp = df.interpolate() # 线性插值,根据邻近值估算
实战建议:处理缺失值前,先问自己“缺失意味着什么?”如果是随机缺失,可以删除或填充;如果是系统性缺失(比如某些字段只在特定条件下填写),可能需要单独标记。
数据筛选与条件查询
# 筛选特定条件的数据
high_sales = df[df['销售额'] > 1000]
# 多条件筛选
filtered = df[(df['城市'] == '北京') & (df['年龄'] > 25)]
# 使用query方法(更可读)
result = df.query('销售额 > 1000 and 城市 == "北京"')
# 查找特定值
has_zhang = df[df['姓名'].str.contains('张')]
分组聚合:数据分析的核心
# 按城市分组,计算平均销售额
avg_sales_by_city = df.groupby('城市')['销售额'].mean()
# 多列聚合
summary = df.groupby('城市').agg({
'销售额': ['mean', 'sum', 'count'],
'利润': 'mean'
})
# 透视表:像Excel一样交叉分析
pivot = pd.pivot_table(df,
values='销售额',
index='城市',
columns='产品类别',
aggfunc='sum')
时间序列处理
日期数据是数据分析的大头。Pandas的to_datetime和日期索引功能非常强大:
# 转换日期列
df['日期'] = pd.to_datetime(df['日期'])
# 设置日期为索引
df.set_index('日期', inplace=True)
# 按月份重采样
monthly_avg = df['销售额'].resample('M').mean()
# 时间范围筛选
week_data = df['2023-01-01':'2023-01-07']
小技巧:用df.dtypes检查每列的数据类型,确保日期列真的被识别为datetime,而不是object。
Matplotlib:让数据“开口说话”
图表是数据分析的终点,也是 communicate 的起点。Matplotlib是最基础的可视化工具,虽然语法略显繁琐,但灵活度极高。
基础图表类型
import matplotlib.pyplot as plt
import numpy as np
# 1. 折线图:展示趋势
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.figure(figsize=(10, 6)) # 设置画布大小
plt.plot(x, y, label='sin(x)', color='blue', linewidth=2)
plt.title('正弦曲线', fontsize=14)
plt.xlabel('x')
plt.ylabel('sin(x)')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()
# 2. 柱状图:比较类别
categories = ['A', 'B', 'C', 'D']
values = [23, 45, 56, 78]
plt.bar(categories, values, color=['#4CAF50', '#2196F3', '#FF9800', '#F44336'])
plt.title('各类别数值比较', fontsize=14)
plt.ylabel('数值')
for i, v in enumerate(values):
plt.text(i, v + 1, str(v), ha='center', fontsize=10)
plt.show()
# 3. 散点图:探索相关性
np.random.seed(42)
x = np.random.randn(100)
y = x + np.random.randn(100) * 0.5
plt.scatter(x, y, alpha=0.6, c='purple', edgecolors='k')
plt.title('x与y的相关性', fontsize=14)
plt.xlabel('x')
plt.ylabel('y')
plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
plt.axvline(0, color='gray', linestyle='--', alpha=0.5)
plt.show()
进阶技巧:子图与自定义
# 创建多子图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
# 子图1:折线图
axes[0, 0].plot([1, 2, 3, 4], [1, 4, 2, 3], 'o-')
axes[0, 0].set_title('子图1')
# 子图2:直方图
data = np.random.randn(1000)
axes[0, 1].hist(data, bins=30, color='skyblue', edgecolor='black')
axes[0, 1].set_title('子图2:正态分布')
# 子图3:箱线图
axes[1, 0].boxplot([np.random.randn(100) for _ in range(3)])
axes[1, 0].set_title('子图3:箱线图')
# 子图4:饼图
sizes = [15, 30, 45, 10]
axes[1, 1].pie(sizes, labels=['A', 'B', 'C', 'D'], autopct='%1.1f%%')
axes[1, 1].set_title('子图4:饼图')
plt.tight_layout()
plt.show()
与Pandas结合:一键绘图
Pandas直接调用Matplotlib,让绘图更简单:
import pandas as pd
import matplotlib.pyplot as plt
# 创建示例数据
df = pd.DataFrame({
'日期': pd.date_range('2023-01-01', periods=12, freq='M'),
'销售额': np.random.randint(1000, 5000, 12),
'利润': np.random.randint(100, 1000, 12)
})
df.set_index('日期', inplace=True)
# 在同一个图上画两条线
df.plot(figsize=(10, 6), linewidth=2)
plt.title('月度销售与利润趋势', fontsize=14)
plt.ylabel('金额')
plt.grid(True, alpha=0.3)
plt.show()
颜色与样式:让图表更专业
# 使用预定义样式
plt.style.use('seaborn-v0_8-darkgrid') # 或 'ggplot', 'bmh', 'fivethirtyeight'
# 自定义颜色映射
import matplotlib.cm as cm
colors = cm.plasma(np.linspace(0, 1, 5)) # 获取渐变颜色
# 保存图表(高清)
plt.savefig('chart.png', dpi=300, bbox_inches='tight')
综合实战:从原始数据到洞察
让我们把三个库结合起来,做一个完整的分析流程。假设你有一堆电商销售数据:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取数据(模拟)
np.random.seed(42)
data = {
'订单ID': range(1, 1001),
'日期': pd.date_range('2023-01-01', periods=1000, freq='H'),
'产品': np.random.choice(['手机', '电脑', '平板', '耳机'], 1000),
'类别': np.random.choice(['电子', '配件'], 1000),
'销售额': np.random.exponential(500, 1000),
'利润': np.random.exponential(100, 1000),
'城市': np.random.choice(['北京', '上海', '广州', '深圳', '杭州'], 1000)
}
df = pd.DataFrame(data)
# 2. 数据清洗
df = df.dropna() # 删除缺失值
df = df[df['销售额'] > 0] # 过滤异常值
# 3. 数据分析
# 按产品汇总
product_summary = df.groupby('产品').agg({
'销售额': ['sum', 'mean'],
'利润': 'sum'
}).round(2)
# 按城市统计
city_sales = df.groupby('城市')['销售额'].sum().sort_values(ascending=False)
# 4. 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 销售额趋势(按小时)
df['小时'] = df['日期'].dt.hour
hourly_sales = df.groupby('小时')['销售额'].sum()
axes[0, 0].plot(hourly_sales, marker='o', color='teal')
axes[0, 0].set_title('每小时销售额趋势')
axes[0, 0].set_xlabel('小时')
axes[0, 0].set_ylabel('销售额')
# 产品销售额对比
axes[0, 1].bar(product_summary.index, product_summary[('销售额', 'sum')], color='coral')
axes[0, 1].set_title('各产品总销售额')
axes[0, 1].set_xlabel('产品')
axes[0, 1].set_ylabel('销售额')
# 城市分布
axes[1, 0].pie(city_sales, labels=city_sales.index, autopct='%1.1f%%',
colors=plt.cm.Set3(np.linspace(0, 1, 5)))
axes[1, 0].set_title('销售额城市分布')
# 利润与销售额关系
axes[1, 1].scatter(df['销售额'], df['利润'], alpha=0.5, c='purple')
axes[1, 1].set_title('利润 vs 销售额')
axes[1, 1].set_xlabel('销售额')
axes[1, 1].set_ylabel('利润')
plt.tight_layout()
plt.show()
# 输出关键洞察
print("=== 分析洞察 ===")
print(f"总销售额:¥{df['销售额'].sum():,.2f}")
print(f"总利润:¥{df['利润'].sum():,.2f}")
print(f"\n销售额最高的产品:{product_summary[('销售额', 'sum')].idxmax()}")
print(f"销售额最高的城市:{city_sales.idxmax()}")
常见陷阱与最佳实践
不要链式赋值:
df['col'][df['col'] > 0] = 1可能不工作。用.loc:df.loc[df['col'] > 0, 'col'] = 1内存管理:处理大文件时,用
chunksize分批读取:for chunk in pd.read_csv('large_file.csv', chunksize=10000): process(chunk)数据类型优化:将
int64转为int32,float64转为float32可以节省内存。避免循环:Pandas向量化操作比
for循环快几十倍。能用groupby就别用iterrows。可视化清晰优先:不要过度装饰图表。标题、标签、图例要清晰,颜色要有区分度但不过于花哨。
下一步:往哪走?
掌握这三个库后
