Skip to content

5.5 数据分析和可视化

概述

🎯 小白理解:什么是"数据分析"和"可视化"?

想象你是一个奶茶店老板:

  • 原始数据:每天的销售记录(日期、产品、数量、金额)
  • 数据分析:从记录中发现规律(周末卖得多、珍珠奶茶最火)
  • 可视化:画成图表,一眼就能看懂趋势
原始数据 (Excel/数据库)
    ↓ 数据分析 (找规律)
洞察 (珍珠奶茶销量增长 30%)
    ↓ 可视化 (画图)
📊 图表 (老板一看就懂)

为什么 Python 适合做数据分析?

  • 库丰富:NumPy、Pandas、Matplotlib 等
  • 代码可复用:同样的分析逻辑,换数据就能用
  • 和 AI 结合:分析结果可以喂给机器学习模型

数据分析和可视化是 Python 最强大的应用领域之一。本节介绍五个核心库:

  • NumPy:高效数值计算
  • Pandas:数据分析和处理
  • Matplotlib:基础绑图库
  • Seaborn:统计可视化
  • Plotly:交互式图表

提示:请在本地 Python 环境中运行代码。建议使用 Jupyter Notebook 以便直接查看图表输出。


NumPy:数值计算基础

🎯 小白理解:为什么需要 NumPy?

Python 自带的列表 list 处理数字很慢,NumPy 的数组快 100 倍!

类比

  • list:一个一个手动算(for 循环)
  • numpy:一次性批量算(向量化运算)
python
# 普通 Python 列表(慢)
result = []
for x in [1, 2, 3, 4]:
    result.append(x * 2)

# NumPy 数组(快 100 倍)
import numpy as np
result = np.array([1, 2, 3, 4]) * 2  # 一行搞定!

核心概念

概念解释类比
ndarrayNumPy 的数组类型高效版的 list
shape数组的形状 (几行几列)矩阵的尺寸
向量化运算不用循环,直接对整个数组操作批处理 vs 单个处理

NumPy 是科学计算的基石,提供高性能的多维数组运算。

数组创建和基本操作

python
import numpy as np

# 创建数组
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([[1, 2, 3], [4, 5, 6]])

print("一维数组:", arr1)
print("二维数组:")
print(arr2)
print(f"形状: {arr2.shape}, 维度: {arr2.ndim}")

# 特殊数组
zeros = np.zeros((3, 3))
ones = np.ones((2, 4))
eye = np.eye(3)  # 单位矩阵

print("\n零矩阵:")
print(zeros)
print("\n单位矩阵:")
print(eye)

# 数值范围
range_arr = np.arange(0, 10, 2)  # [0, 2, 4, 6, 8]
linspace_arr = np.linspace(0, 1, 5)  # 等间距 5 个点

print(f"\narange: {range_arr}")
print(f"linspace: {linspace_arr}")

输出

一维数组: [1 2 3 4 5]
二维数组:
[[1 2 3]
 [4 5 6]]
形状: (2, 3), 维度: 2

零矩阵:
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]

单位矩阵:
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

arange: [0 2 4 6 8]
linspace: [0.   0.25 0.5  0.75 1.  ]

数组运算

python
import numpy as np

# 向量化运算(比 Python 循环快 100 倍)
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])

print("加法:", a + b)
print("乘法:", a * b)
print("平方:", a ** 2)
print("求和:", np.sum(a))
print("均值:", np.mean(a))
print("标准差:", np.std(a))

# 矩阵运算
matrix_a = np.array([[1, 2], [3, 4]])
matrix_b = np.array([[5, 6], [7, 8]])

print("\n矩阵乘法:")
print(np.dot(matrix_a, matrix_b))  # 或 matrix_a @ matrix_b

# 统计函数
data = np.random.randn(1000)  # 1000 个标准正态分布随机数
print(f"\n随机数统计:")
print(f"均值: {np.mean(data):.4f}")
print(f"标准差: {np.std(data):.4f}")
print(f"最小值: {np.min(data):.4f}")
print(f"最大值: {np.max(data):.4f}")

输出

加法: [11 22 33 44]
乘法: [ 10  40  90 160]
平方: [ 1  4  9 16]
求和: 10
均值: 2.5
标准差: 1.118033988749895

矩阵乘法:
[[19 22]
 [43 50]]

随机数统计:
均值: 0.0156
标准差: 1.0023
最小值: -3.2145
最大值: 3.1876

Pandas:数据分析利器

🎯 小白理解:Pandas 是什么?

Pandas = Python 版的 Excel,但更强大!

ExcelPandas优势
手动操作写代码自动化可重复执行
百万行就卡处理更大数据性能更好
复杂公式难维护代码清晰可读易于调试

核心概念

  • DataFrame:二维表格(像 Excel 的工作表)
  • Series:一列数据(像 Excel 的一列)
DataFrame 结构:

     姓名    年龄    城市     薪资
0    张三    25     北京    15000   ← 行 (row)
1    李四    30     上海    20000
2    王五    35     广州    18000

    列 (column)

常用操作

  • df.head() → 看前几行
  • df['列名'] → 选择一列
  • df[df['年龄'] > 28] → 条件筛选
  • df.groupby('部门').mean() → 分组统计

Pandas 提供 DataFrame 数据结构,是数据分析的标准工具。

创建和查看数据

python
import pandas as pd
import numpy as np

# 从字典创建 DataFrame
data = {
    '姓名': ['张三', '李四', '王五', '赵六', '钱七'],
    '年龄': [25, 30, 35, 28, 32],
    '城市': ['北京', '上海', '广州', '深圳', '杭州'],
    '薪资': [15000, 20000, 18000, 22000, 25000]
}

df = pd.DataFrame(data)

print("DataFrame 内容:")
print(df)
print(f"\n数据形状: {df.shape}")
print(f"\n数据类型:\n{df.dtypes}")
print(f"\n统计摘要:\n{df.describe()}")

输出

DataFrame 内容:
   姓名  年龄  城市    薪资
0  张三  25  北京  15000
1  李四  30  上海  20000
2  王五  35  广州  18000
3  赵六  28  深圳  22000
4  钱七  32  杭州  25000

数据形状: (5, 4)

数据类型:
姓名    object
年龄     int64
城市    object
薪资     int64
dtype: object

统计摘要:
              年龄           薪资
count   5.000000      5.000000
mean   30.000000  20000.000000
std     3.807887   3807.886553
min    25.000000  15000.000000
25%    28.000000  18000.000000
50%    30.000000  20000.000000
75%    32.000000  22000.000000
max    35.000000  25000.000000

数据筛选和操作

python
import pandas as pd

# 创建示例数据
data = {
    '姓名': ['张三', '李四', '王五', '赵六', '钱七'],
    '年龄': [25, 30, 35, 28, 32],
    '部门': ['技术', '销售', '技术', '销售', '技术'],
    '薪资': [15000, 20000, 18000, 22000, 25000]
}
df = pd.DataFrame(data)

# 选择列
print("选择单列:")
print(df['姓名'])

print("\n选择多列:")
print(df[['姓名', '薪资']])

# 条件筛选
print("\n年龄大于 28 的员工:")
print(df[df['年龄'] > 28])

print("\n技术部门的员工:")
print(df[df['部门'] == '技术'])

# 多条件筛选
print("\n技术部门且薪资大于 17000:")
print(df[(df['部门'] == '技术') & (df['薪资'] > 17000)])

# 添加新列
df['年薪'] = df['薪资'] * 12
print("\n添加年薪列:")
print(df)

# 分组统计
print("\n按部门统计平均薪资:")
print(df.groupby('部门')['薪资'].mean())

读写文件

python
import pandas as pd

# 创建示例数据并保存
data = {
    '产品': ['手机', '电脑', '平板', '耳机'],
    '销量': [1000, 500, 800, 1500],
    '单价': [5000, 8000, 3000, 500]
}
df = pd.DataFrame(data)

# 保存为 CSV
df.to_csv('sales_data.csv', index=False, encoding='utf-8')
print("已保存为 sales_data.csv")

# 读取 CSV
df_loaded = pd.read_csv('sales_data.csv', encoding='utf-8')
print("\n读取的数据:")
print(df_loaded)

# 保存为 JSON
df.to_json('sales_data.json', orient='records', force_ascii=False, indent=2)
print("\n已保存为 sales_data.json")

Matplotlib:基础绑图

🎯 小白理解:Matplotlib 是什么?

Matplotlib = Python 的画图工具,可以画各种统计图表。

常见图表类型

图表用途适合场景
折线图展示趋势变化股票走势、销售趋势
柱状图对比大小各部门业绩对比
饼图展示占比市场份额分布
散点图看变量关系身高 vs 体重

基本套路(记住这个模式)

python
import matplotlib.pyplot as plt

# 1. 创建画布
plt.figure(figsize=(10, 6))

# 2. 画图
plt.plot(x, y)  # 折线图
# plt.bar(x, y)  # 柱状图
# plt.pie(sizes)  # 饼图

# 3. 添加标题和标签
plt.title('标题')
plt.xlabel('X轴')
plt.ylabel('Y轴')

# 4. 保存/显示
plt.savefig('图片.png')
plt.show()

Matplotlib 是 Python 最基础的绑图库,功能强大且灵活。

折线图

python
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import numpy as np

# 数据
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
sales_a = [120, 135, 150, 145, 160, 180]
sales_b = [100, 115, 125, 140, 135, 155]

# 创建图形
plt.figure(figsize=(10, 6))
plt.plot(months, sales_a, marker='o', linewidth=2, label='Product A')
plt.plot(months, sales_b, marker='s', linewidth=2, label='Product B')

plt.title('2024 H1 Sales Trend', fontsize=16)
plt.xlabel('Month', fontsize=12)
plt.ylabel('Sales (10K CNY)', fontsize=12)
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)

plt.tight_layout()
plt.savefig('line_chart.png', dpi=150)
plt.show()

输出图表:显示两条折线分别代表产品A和产品B的销售趋势。

柱状图

python
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import numpy as np

# 数据
categories = ['Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen', 'Hangzhou']
values_2023 = [85, 92, 78, 88, 70]
values_2024 = [95, 98, 85, 95, 82]

x = np.arange(len(categories))
width = 0.35

# 创建图形
fig, ax = plt.subplots(figsize=(10, 6))
bars1 = ax.bar(x - width/2, values_2023, width, label='2023', color='steelblue')
bars2 = ax.bar(x + width/2, values_2024, width, label='2024', color='coral')

ax.set_title('City Sales Comparison', fontsize=16)
ax.set_xlabel('City', fontsize=12)
ax.set_ylabel('Sales (100M CNY)', fontsize=12)
ax.set_xticks(x)
ax.set_xticklabels(categories)
ax.legend()

# 添加数值标签
for bar in bars1:
    height = bar.get_height()
    ax.annotate(f'{height}',
                xy=(bar.get_x() + bar.get_width() / 2, height),
                xytext=(0, 3),
                textcoords="offset points",
                ha='center', va='bottom', fontsize=9)

for bar in bars2:
    height = bar.get_height()
    ax.annotate(f'{height}',
                xy=(bar.get_x() + bar.get_width() / 2, height),
                xytext=(0, 3),
                textcoords="offset points",
                ha='center', va='bottom', fontsize=9)

plt.tight_layout()
plt.savefig('bar_chart.png', dpi=150)
plt.show()

饼图

python
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt

# 数据
labels = ['Tech', 'Sales', 'Marketing', 'HR', 'Finance']
sizes = [35, 25, 20, 12, 8]
colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#ff99cc']
explode = (0.05, 0, 0, 0, 0)  # Highlight first slice

# 创建图形
fig, ax = plt.subplots(figsize=(8, 8))
wedges, texts, autotexts = ax.pie(
    sizes,
    explode=explode,
    labels=labels,
    colors=colors,
    autopct='%1.1f%%',
    shadow=True,
    startangle=90
)

ax.set_title('Department Staff Distribution', fontsize=16)
plt.setp(autotexts, size=11, weight='bold')

plt.tight_layout()
plt.savefig('pie_chart.png', dpi=150)
plt.show()

散点图

python
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import numpy as np

# 生成模拟数据
np.random.seed(42)
n = 100
x = np.random.randn(n) * 10 + 50  # Work experience (months)
y = x * 200 + np.random.randn(n) * 2000 + 5000  # Salary

# 创建图形
plt.figure(figsize=(10, 6))
scatter = plt.scatter(x, y, c=y, cmap='viridis', alpha=0.7, s=60)
plt.colorbar(scatter, label='Salary Level')

# 添加趋势线
z = np.polyfit(x, y, 1)
p = np.poly1d(z)
plt.plot(x, p(x), "r--", linewidth=2, label=f'Trend: y={z[0]:.1f}x+{z[1]:.1f}')

plt.title('Work Experience vs Salary', fontsize=16)
plt.xlabel('Experience (months)', fontsize=12)
plt.ylabel('Monthly Salary (CNY)', fontsize=12)
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('scatter_chart.png', dpi=150)
plt.show()

子图布局

python
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import numpy as np

# 准备数据
np.random.seed(42)
x = np.linspace(0, 10, 100)

# 创建 2x2 子图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# 子图1: 正弦波
axes[0, 0].plot(x, np.sin(x), 'b-', linewidth=2)
axes[0, 0].set_title('Sine Function')
axes[0, 0].set_xlabel('x')
axes[0, 0].set_ylabel('sin(x)')
axes[0, 0].grid(True, alpha=0.3)

# 子图2: 直方图
data = np.random.randn(1000)
axes[0, 1].hist(data, bins=30, color='green', alpha=0.7, edgecolor='black')
axes[0, 1].set_title('Normal Distribution Histogram')
axes[0, 1].set_xlabel('Value')
axes[0, 1].set_ylabel('Frequency')

# 子图3: 柱状图
categories = ['A', 'B', 'C', 'D', 'E']
values = [23, 45, 56, 78, 32]
axes[1, 0].bar(categories, values, color='coral')
axes[1, 0].set_title('Category Data')
axes[1, 0].set_xlabel('Category')
axes[1, 0].set_ylabel('Value')

# 子图4: 箱线图
data_box = [np.random.randn(100) + i for i in range(4)]
axes[1, 1].boxplot(data_box, labels=['Group1', 'Group2', 'Group3', 'Group4'])
axes[1, 1].set_title('Box Plot Comparison')
axes[1, 1].set_ylabel('Value')

plt.suptitle('Matplotlib Subplots Example', fontsize=16, y=1.02)
plt.tight_layout()
plt.savefig('subplot_chart.png', dpi=150)
plt.show()

Seaborn:统计可视化

🎯 小白理解:Seaborn 和 Matplotlib 有什么区别?

Matplotlib 是"基础款",Seaborn 是"高级款"

特性MatplotlibSeaborn
美观度需要手动调整默认就好看
代码量较多较少
统计功能需要自己算内置统计
适合场景精细控制快速探索

例子对比

python
# Matplotlib(10+ 行代码)
plt.figure()
plt.scatter(x, y)
plt.xlabel('X')
plt.ylabel('Y')
# ... 还要加趋势线、颜色等

# Seaborn(1 行代码)
sns.regplot(x='X', y='Y', data=df)  # 自带趋势线!

常用 Seaborn 图表

  • histplot → 直方图(看分布)
  • boxplot → 箱线图(看离群值)
  • heatmap → 热力图(看相关性)
  • pairplot → 变量关系矩阵

Seaborn 基于 Matplotlib,提供更美观的统计图表和更简洁的 API。

分布图

python
import seaborn as sns
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

sns.set_style("whitegrid")

# 生成模拟数据
np.random.seed(42)
data = pd.DataFrame({
    'Age': np.random.normal(35, 10, 500).astype(int),
    'Income': np.random.normal(50000, 15000, 500),
    'Gender': np.random.choice(['Male', 'Female'], 500)
})

# 创建 1x2 子图
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 直方图 + 核密度估计
sns.histplot(data=data, x='Age', kde=True, ax=axes[0], color='steelblue')
axes[0].set_title('Age Distribution', fontsize=14)

# 分组直方图
sns.histplot(data=data, x='Income', hue='Gender', kde=True, ax=axes[1])
axes[1].set_title('Income Distribution by Gender', fontsize=14)

plt.tight_layout()
plt.savefig('seaborn_dist.png', dpi=150)
plt.show()

关系图

python
import seaborn as sns
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

sns.set_style("whitegrid")

# 生成模拟数据
np.random.seed(42)
n = 200
data = pd.DataFrame({
    'AdSpend': np.random.uniform(10, 100, n),
    'Revenue': None,
    'Region': np.random.choice(['East', 'South', 'North', 'West'], n)
})
data['Revenue'] = data['AdSpend'] * 2.5 + np.random.normal(0, 20, n) + 50

# 创建关系图
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 散点图 + 回归线
sns.regplot(data=data, x='AdSpend', y='Revenue', ax=axes[0],
            scatter_kws={'alpha': 0.5}, line_kws={'color': 'red'})
axes[0].set_title('Ad Spend vs Revenue (with Regression)', fontsize=14)

# 按类别着色的散点图
sns.scatterplot(data=data, x='AdSpend', y='Revenue', hue='Region',
                style='Region', s=80, ax=axes[1])
axes[1].set_title('Ad Spend vs Revenue by Region', fontsize=14)

plt.tight_layout()
plt.savefig('seaborn_relation.png', dpi=150)
plt.show()

分类图

python
import seaborn as sns
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

sns.set_style("whitegrid")

# 生成模拟员工数据
np.random.seed(42)
data = pd.DataFrame({
    'Dept': np.repeat(['Tech', 'Sales', 'Marketing', 'HR'], 50),
    'Salary': np.concatenate([
        np.random.normal(18000, 3000, 50),
        np.random.normal(15000, 4000, 50),
        np.random.normal(14000, 2500, 50),
        np.random.normal(12000, 2000, 50)
    ]),
    'YearsExp': np.random.randint(1, 10, 200)
})

# 创建 2x2 子图
fig, axes = plt.subplots(2, 2, figsize=(14, 12))

# 箱线图
sns.boxplot(data=data, x='Dept', y='Salary', ax=axes[0, 0], palette='Set2')
axes[0, 0].set_title('Salary by Department (Box Plot)', fontsize=14)

# 小提琴图
sns.violinplot(data=data, x='Dept', y='Salary', ax=axes[0, 1], palette='Set3')
axes[0, 1].set_title('Salary by Department (Violin Plot)', fontsize=14)

# 带散点的箱线图
sns.boxplot(data=data, x='Dept', y='Salary', ax=axes[1, 0], palette='pastel')
sns.stripplot(data=data, x='Dept', y='Salary', ax=axes[1, 0],
              color='black', alpha=0.3, size=3)
axes[1, 0].set_title('Salary Distribution (with Points)', fontsize=14)

# 条形图(显示均值和置信区间)
sns.barplot(data=data, x='Dept', y='Salary', ax=axes[1, 1],
            palette='coolwarm', errorbar='ci')
axes[1, 1].set_title('Average Salary by Department', fontsize=14)

plt.tight_layout()
plt.savefig('seaborn_category.png', dpi=150)
plt.show()

热力图

python
import seaborn as sns
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# 创建相关矩阵数据
np.random.seed(42)
n = 200
data = pd.DataFrame({
    'Age': np.random.randint(22, 60, n),
    'YearsExp': np.random.randint(0, 30, n),
    'EduLevel': np.random.randint(1, 5, n),
    'PerfScore': np.random.uniform(60, 100, n),
    'Salary': np.random.normal(15000, 5000, n)
})

# 计算相关系数
correlation = data.corr()

# 创建热力图
plt.figure(figsize=(10, 8))
sns.heatmap(correlation,
            annot=True,  # 显示数值
            fmt='.2f',   # 保留两位小数
            cmap='coolwarm',  # 颜色方案
            center=0,    # 颜色中心点
            square=True, # 正方形单元格
            linewidths=0.5)

plt.title('Variable Correlation Heatmap', fontsize=16)
plt.tight_layout()
plt.savefig('seaborn_heatmap.png', dpi=150)
plt.show()

成对关系图

python
import seaborn as sns
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# 创建数据
np.random.seed(42)
n = 150
species = np.repeat(['TypeA', 'TypeB', 'TypeC'], 50)
data = pd.DataFrame({
    'Feature1': np.concatenate([
        np.random.normal(5, 1, 50),
        np.random.normal(7, 1.2, 50),
        np.random.normal(6, 0.8, 50)
    ]),
    'Feature2': np.concatenate([
        np.random.normal(3, 0.5, 50),
        np.random.normal(4, 0.8, 50),
        np.random.normal(5, 0.6, 50)
    ]),
    'Feature3': np.concatenate([
        np.random.normal(1.5, 0.3, 50),
        np.random.normal(4, 0.5, 50),
        np.random.normal(5.5, 0.4, 50)
    ]),
    'Category': species
})

# 创建成对关系图
pairplot = sns.pairplot(data, hue='Category', palette='husl',
                        diag_kind='kde', plot_kws={'alpha': 0.6})
pairplot.fig.suptitle('Multi-variable Pair Plot', y=1.02, fontsize=16)

plt.savefig('seaborn_pairplot.png', dpi=150)
plt.show()

Plotly:交互式可视化

🎯 小白理解:什么是"交互式"图表?

普通图表 vs 交互式图表

普通图表 (Matplotlib)交互式图表 (Plotly)
静态图片,不能动可以缩放、拖动
只能看固定视角鼠标悬停显示详情
保存为 PNG/PDF保存为 HTML(网页)
适合打印报告适合网页展示

交互式图表能做什么?

  1. 缩放:看细节时放大,看全局时缩小
  2. 悬停:鼠标放上去,显示具体数值
  3. 筛选:点击图例,隐藏/显示某类数据
  4. 3D 旋转:换角度观察立体图表

什么时候用 Plotly?

  • 做 Dashboard(数据看板)
  • 需要让用户自己探索数据
  • 在网页中展示图表

Plotly 创建可交互的动态图表,支持缩放、悬停提示等功能。

基础折线图

python
import plotly.express as px
import pandas as pd
import numpy as np

# 准备数据
np.random.seed(42)
months = pd.date_range('2024-01-01', periods=12, freq='M')
data = pd.DataFrame({
    'Date': months,
    'Sales': np.cumsum(np.random.randint(80, 150, 12)) + 1000,
    'Profit': np.cumsum(np.random.randint(20, 50, 12)) + 200
})

# 创建折线图
fig = px.line(data, x='Date', y=['Sales', 'Profit'],
              title='2024 Monthly Sales Trend',
              labels={'value': 'Amount (10K CNY)', 'variable': 'Metric'})

fig.update_layout(
    hovermode='x unified',
    legend=dict(orientation='h', yanchor='bottom', y=1.02)
)

# 保存为 HTML(可交互)
fig.write_html('plotly_line.html')
# 也可以保存为静态图片
# fig.write_image('plotly_line.png')
fig.show()

交互式散点图

python
import plotly.express as px
import pandas as pd
import numpy as np

# 生成数据
np.random.seed(42)
n = 100
data = pd.DataFrame({
    'AdSpend': np.random.uniform(10, 100, n),
    'Revenue': None,
    'Category': np.random.choice(['Electronics', 'Clothing', 'Food', 'Home'], n),
    'Customers': np.random.randint(100, 1000, n)
})
data['Revenue'] = data['AdSpend'] * 3 + np.random.normal(0, 15, n) + 50

# 创建散点图
fig = px.scatter(
    data,
    x='AdSpend',
    y='Revenue',
    color='Category',
    size='Customers',
    hover_data=['Customers'],
    title='Ad Spend vs Revenue',
    labels={'AdSpend': 'Ad Spend (10K CNY)', 'Revenue': 'Revenue (10K CNY)'}
)

fig.update_layout(
    legend=dict(orientation='h', yanchor='bottom', y=1.02)
)

fig.write_html('plotly_scatter.html')
fig.show()

交互式柱状图

python
import plotly.express as px
import pandas as pd
import numpy as np

# 准备数据
data = pd.DataFrame({
    'Quarter': ['Q1', 'Q2', 'Q3', 'Q4'] * 3,
    'Region': ['East'] * 4 + ['South'] * 4 + ['North'] * 4,
    'Sales': [120, 135, 128, 145, 98, 112, 105, 125, 85, 95, 92, 108]
})

# 创建分组柱状图
fig = px.bar(
    data,
    x='Quarter',
    y='Sales',
    color='Region',
    barmode='group',
    title='Regional Quarterly Sales Comparison',
    labels={'Sales': 'Sales (10K CNY)'},
    text='Sales'
)

fig.update_traces(textposition='outside')
fig.update_layout(uniformtext_minsize=8, uniformtext_mode='hide')

fig.write_html('plotly_bar.html')
fig.show()

饼图和环形图

python
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd

# 准备数据
data = pd.DataFrame({
    'Dept': ['Tech', 'Sales', 'Marketing', 'Operations', 'HR'],
    'Ratio': [35, 25, 18, 15, 7]
})

# 创建子图:饼图和环形图
fig = make_subplots(rows=1, cols=2,
                    specs=[[{'type': 'pie'}, {'type': 'pie'}]],
                    subplot_titles=['Pie Chart', 'Donut Chart'])

# 饼图
fig.add_trace(
    go.Pie(labels=data['Dept'], values=data['Ratio'], name='Pie'),
    row=1, col=1
)

# 环形图
fig.add_trace(
    go.Pie(labels=data['Dept'], values=data['Ratio'], hole=0.4, name='Donut'),
    row=1, col=2
)

fig.update_layout(title_text='Staff Distribution by Department', showlegend=True)

fig.write_html('plotly_pie.html')
fig.show()

热力图

python
import plotly.express as px
import pandas as pd
import numpy as np

# 生成销售数据矩阵
np.random.seed(42)
products = ['Product A', 'Product B', 'Product C', 'Product D', 'Product E']
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']

sales_matrix = np.random.randint(50, 200, (len(products), len(months)))

# 创建 DataFrame
data = pd.DataFrame(sales_matrix, index=products, columns=months)

# 创建热力图
fig = px.imshow(
    data,
    labels=dict(x='Month', y='Product', color='Sales'),
    title='Product Monthly Sales Heatmap',
    color_continuous_scale='RdYlGn',
    text_auto=True
)

fig.update_layout(
    xaxis_title='Month',
    yaxis_title='Product'
)

fig.write_html('plotly_heatmap.html')
fig.show()

3D 散点图

python
import plotly.express as px
import pandas as pd
import numpy as np

# 生成 3D 数据
np.random.seed(42)
n = 200
data = pd.DataFrame({
    'X': np.random.randn(n),
    'Y': np.random.randn(n),
    'Z': np.random.randn(n),
    'Category': np.random.choice(['A', 'B', 'C'], n)
})

# 创建 3D 散点图
fig = px.scatter_3d(
    data,
    x='X', y='Y', z='Z',
    color='Category',
    title='3D Data Distribution',
    opacity=0.7
)

fig.update_layout(
    scene=dict(
        xaxis_title='X Axis',
        yaxis_title='Y Axis',
        zaxis_title='Z Axis'
    )
)

fig.write_html('plotly_3d.html')
fig.show()

综合案例:销售数据分析报告

以下是一个完整的数据分析和可视化案例:

python
import numpy as np
import pandas as pd
import matplotlib
# matplotlib.use('Agg') # 如果在无图形界面的环境运行,请取消注释
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_style("whitegrid")

# 1. 生成模拟销售数据
np.random.seed(42)
n = 500

data = pd.DataFrame({
    'OrderID': range(1, n + 1),
    'Date': pd.date_range('2024-01-01', periods=n, freq='D')[:n],
    'Category': np.random.choice(['Electronics', 'Clothing', 'Food', 'Home'], n),
    'Channel': np.random.choice(['Online', 'Offline'], n, p=[0.6, 0.4]),
    'Revenue': np.random.exponential(500, n) + 100,
    'Quantity': np.random.randint(1, 10, n),
    'Rating': np.random.uniform(3.0, 5.0, n)
})

# 添加月份列
data['Month'] = data['Date'].dt.to_period('M').astype(str)

print("Data Preview:")
print(data.head(10))
print(f"\nDataset Size: {data.shape}")

# 2. 数据统计分析
print("\n" + "=" * 50)
print("Statistical Analysis")
print("=" * 50)

print("\nBasic Statistics:")
print(data.describe())

print("\nBy Category:")
category_stats = data.groupby('Category').agg({
    'Revenue': ['sum', 'mean', 'count'],
    'Rating': 'mean'
}).round(2)
print(category_stats)

print("\nBy Channel:")
channel_stats = data.groupby('Channel').agg({
    'Revenue': ['sum', 'mean'],
    'Quantity': 'sum'
}).round(2)
print(channel_stats)

# 3. 可视化分析
fig, axes = plt.subplots(2, 3, figsize=(18, 12))

# 图1: 月度销售趋势
monthly_sales = data.groupby('Month')['Revenue'].sum().reset_index()
axes[0, 0].plot(monthly_sales['Month'], monthly_sales['Revenue'],
                marker='o', linewidth=2, color='steelblue')
axes[0, 0].set_title('Monthly Sales Trend', fontsize=14)
axes[0, 0].set_xlabel('Month')
axes[0, 0].set_ylabel('Revenue (CNY)')
axes[0, 0].tick_params(axis='x', rotation=45)

# 图2: 产品类别销售占比
category_sales = data.groupby('Category')['Revenue'].sum()
colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99']
axes[0, 1].pie(category_sales, labels=category_sales.index, autopct='%1.1f%%',
               colors=colors, startangle=90)
axes[0, 1].set_title('Revenue by Category', fontsize=14)

# 图3: 销售渠道对比
channel_sales = data.groupby('Channel')['Revenue'].sum()
axes[0, 2].bar(channel_sales.index, channel_sales.values,
               color=['coral', 'steelblue'])
axes[0, 2].set_title('Revenue by Channel', fontsize=14)
axes[0, 2].set_ylabel('Revenue (CNY)')
for i, v in enumerate(channel_sales.values):
    axes[0, 2].text(i, v + 1000, f'{v:,.0f}', ha='center')

# 图4: 销售额分布
sns.histplot(data=data, x='Revenue', kde=True, ax=axes[1, 0], color='steelblue')
axes[1, 0].set_title('Revenue Distribution', fontsize=14)
axes[1, 0].set_xlabel('Revenue (CNY)')

# 图5: 各类别客户评分箱线图
sns.boxplot(data=data, x='Category', y='Rating', ax=axes[1, 1], palette='Set2')
axes[1, 1].set_title('Rating by Category', fontsize=14)
axes[1, 1].tick_params(axis='x', rotation=15)

# 图6: 销售额与数量关系
sns.scatterplot(data=data, x='Quantity', y='Revenue', hue='Channel',
                ax=axes[1, 2], alpha=0.6)
axes[1, 2].set_title('Revenue vs Quantity', fontsize=14)
axes[1, 2].set_xlabel('Quantity')
axes[1, 2].set_ylabel('Revenue (CNY)')

plt.suptitle('Sales Data Analysis Report', fontsize=18, y=1.02)
plt.tight_layout()
plt.savefig('sales_analysis_report.png', dpi=150, bbox_inches='tight')
plt.show()

安装指南

bash
# 使用 pip 安装
pip install numpy pandas matplotlib seaborn plotly

# 使用 conda 安装
conda install numpy pandas matplotlib seaborn plotly

# Jupyter Notebook 中显示 Plotly 图表
pip install nbformat

小结

本节介绍了 Python 数据分析和可视化的五大核心库:

用途特点
NumPy数值计算高效数组运算,科学计算基础
Pandas数据分析DataFrame 结构,数据处理利器
Matplotlib基础绑图功能全面,高度可定制
Seaborn统计可视化美观简洁,统计图表
Plotly交互式图表动态交互,支持 Web

最佳实践

  • 数据处理首选 Pandas + NumPy
  • 快速探索用 Seaborn
  • 精细控制用 Matplotlib
  • 交互式展示用 Plotly

下一节:5.6 小结和复习

基于 MIT 许可证发布。内容版权归作者所有。