# Python数据分析全栈教程:从环境搭建到Kaggle实战
在开始数据处理与统计分析之前,我们需要搭建一个稳定、可复现的 Python 环境。本部分将详细介绍如何在 Ubuntu 24.04 LTS 上配置 Python 虚拟环境、安装 JupyterLab 并设置其为系统服务以实现开机自启动,同时安装常用的数据科学包。所有操作均基于终端命令行,并遵循系统管理员的最佳实践。
# 1.1 系统准备
# 1.1.1 更新系统包
确保系统包列表和已安装的软件包是最新的:
sudo apt update && sudo apt upgrade -y
# 1.1.2 安装 Python 及虚拟环境工具
Ubuntu 24.04 默认包含 Python 3.12,我们只需安装 pip 和虚拟环境管理工具:
sudo apt install python3 python3-pip python3-venv -y
# 1.2 创建独立 Python 虚拟环境
为了避免项目间的依赖冲突,我们为数据分析项目创建一个专用的虚拟环境。这里以 /opt/hep-jupyter 作为环境目录。
# 创建目录并将所有权赋予当前用户(假设使用普通用户,若为 root 可跳过 chown)
sudo mkdir -p /opt/hep-jupyter
sudo chown $USER:$USER /opt/hep-jupyter
cd /opt/hep-jupyter
2
3
4
创建 Python 虚拟环境:
python3 -m venv venv
激活虚拟环境(后续所有包安装均在此环境中进行):
source venv/bin/activate
激活后,命令行提示符前会出现(venv)标识。
# 1.3 安装 JupyterLab 及其依赖
JupyterLab 是下一代 Jupyter Notebook 的交互式开发环境,适合数据科学工作流。
# 1.3.1 安装 Node.js(JupyterLab 扩展所需)
某些 JupyterLab 扩展需要 Node.js 运行时,我们一并安装:
sudo apt install nodejs npm -y
# 1.3.2 安装 JupyterLab
在虚拟环境中使用 pip 安装 JupyterLab:
pip install jupyterlab
如需经典 Jupyter Notebook,可替换为 jupyter notebook,但本教程推荐 JupyterLab。
# 1.4 配置 JupyterLab
# 1.4.1 生成配置文件
运行以下命令生成 Jupyter 配置文件:
jupyter server --generate-config
配置文件默认生成在 ~/.jupyter/jupyter_server_config.py。
# 1.4.2 设置访问密码
为了安全,建议设置登录密码:
jupyter server password
根据提示输入并确认密码,密码的哈希值将保存在 ~/.jupyter/jupyter_server_config.json 中。
# 1.4.3 创建工作目录
创建一个用于存放 Notebook 文件的工作目录,例如 /usr/software/jupyter:
sudo mkdir -p /usr/software/jupyter
sudo chown $USER:$USER /usr/software/jupyter # 赋予当前用户权限
2
# 1.4.4 编辑配置文件
使用文本编辑器修改 ~/.jupyter/jupyter_server_config.py,添加以下配置:
# 监听所有网络接口(允许远程访问)
c.ServerApp.ip = '0.0.0.0'
# 指定端口
c.ServerApp.port = 8888
# 启动时不自动打开浏览器
c.ServerApp.open_browser = False
# 设置工作目录
c.ServerApp.root_dir = '/usr/software/jupyter'
# 如果需要允许 root 用户运行(仅当使用 root 时开启,存在安全风险)
c.ServerApp.allow_root = True
# 允许远程访问
c.ServerApp.allow_remote_access = True
# 可选:关闭跨站请求伪造保护(仅在内网环境)
# c.ServerApp.disable_check_xsrf = True
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1.5 设置开机自启动
为了在服务器重启后自动启动 JupyterLab,我们创建一个 systemd 服务。
# 1.5.1 创建服务单元文件
使用 sudo 创建 /etc/systemd/system/hep-jupyter.service 文件:
sudo nano /etc/systemd/system/hep-jupyter.service
填入以下内容:
[Unit]
Description=HEP JupyterLab Service
After=network.target
[Service]
Type=simple
User=root
Group=root
# 如果使用 root,需加载环境变量(可选)
Environment="PATH=/opt/hep-jupyter/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
WorkingDirectory=/opt/hep-jupyter
# 启动命令:直接调用虚拟环境中的 jupyter lab
ExecStart=/opt/hep-jupyter/venv/bin/jupyter lab --no-browser --ip=0.0.0.0 --port=8888 --notebook-dir=/usr/software/jupyter
# 重启策略
Restart=on-failure
RestartSec=5
TimeoutSec=30
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
User和Group:如果使用普通用户,请替换为实际的用户名和组名(如ubuntu)。若使用 root,则改为root。Environment:确保虚拟环境的bin目录在 PATH 中,以便正确找到可执行文件。ExecStart:直接指定虚拟环境中 jupyter 的完整路径,避免依赖系统 PATH。
# 1.5.2 启用并启动服务
# 重新加载 systemd 配置
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start hep-jupyter.service
# 设置开机自启
sudo systemctl enable hep-jupyter.service
# 查看服务状态
sudo systemctl status hep-jupyter.service
2
3
4
5
6
7
8
9
10
11
如果状态显示为 active (running),则服务启动成功。
# 1.6 防火墙配置
如果需要从外部访问 JupyterLab,必须开放配置的端口(如 8888)。若系统启用了 UFW 防火墙,执行:
sudo ufw allow 8888/tcp
sudo ufw reload
2
现在,你可以通过浏览器访问 http://<服务器IP>:8888,输入之前设置的密码即可进入 JupyterLab 界面。
# 1.7 安装常用数据科学包
在虚拟环境中安装本教程后续需要用到的 Python 包:
# 确保虚拟环境已激活
pip install numpy pandas matplotlib seaborn pyecharts pillow jupyter-echarts
# 一些工具包
pip install openpyxl pymysql sqlalchemy
# 可选安装:用于机器学习和统计建模的包
pip install scipy scikit-learn statsmodels
# 如需从 Kaggle 下载数据,可安装 kaggle 包
pip install kaggle
# 常用深度学习库
pip install tensorflow opencv-python torch imutils
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1.8 安装 JupyterLab 扩展插件
JupyterLab 支持丰富的扩展,以下安装两个常用插件:
- 目录导航(Table of Contents):
jupyterlab-toc - LaTeX 支持:
jupyterlab-latex
pip install jupyterlab-toc jupyterlab-latex
安装完成后,重启 JupyterLab 服务以使扩展生效:
sudo systemctl restart hep-jupyter.service
# 1.9 验证安装
在浏览器中打开 JupyterLab,新建一个 Python 3 笔记本,运行以下代码测试关键库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import pyecharts
print("NumPy version:", np.__version__)
print("Pandas version:", pd.__version__)
print("Matplotlib version:", plt.matplotlib.__version__)
print("Seaborn version:", sns.__version__)
print("Pyecharts version:", pyecharts.__version__)
2
3
4
5
6
7
8
9
10
11
若正常输出版本号,则环境搭建成功。
# 1.10 服务管理与日志查看
- 停止服务:
sudo systemctl stop hep-jupyter.service - 重启服务:
sudo systemctl restart hep-jupyter.service - 查看实时日志:
sudo journalctl -u hep-jupyter.service -f
至此,您已经拥有了一个功能完备、可远程访问且开机自启动的 JupyterLab 数据分析环境。后续章节的所有代码示例均可在该环境中运行。
# 2. NumPy详解
NumPy(Numerical Python)是 Python 科学计算的基础库,提供了高性能的多维数组对象及丰富的操作函数。本部分将详细讲解NumPy的核心概念与常用功能。
# 2.1 NumPy数组基础
# 2.1.1 创建数组
NumPy的主要对象是同质多维数组(ndarray)。创建数组的常用方法:
import numpy as np
# 从列表创建
a = np.array([1, 2, 3])
print(a, a.shape, a.dtype) # [1 2 3] (3,) int64
# 二维数组
b = np.array([[1, 2], [3, 4]])
print(b)
# [[1 2]
# [3 4]]
# 特殊数组
zeros = np.zeros((2, 3)) # 全0
ones = np.ones((3, 2)) # 全1
empty = np.empty((2, 2)) # 未初始化(值随机)
full = np.full((2, 2), 7) # 全部填充7
eye = np.eye(3) # 单位矩阵
rng = np.arange(0, 10, 2) # 等差序列 [0 2 4 6 8]
lin = np.linspace(0, 1, 5) # 等分点 [0. 0.25 0.5 0.75 1. ]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
shape属性返回一个表示数组各维度大小的元组。dtype属性返回数组元素的数据类型,如int32、float64等。
# 2.1.2 数据类型
NumPy支持多种数值类型,可通过 dtype 参数指定:
x = np.array([1, 2, 3], dtype=np.float32)
print(x.dtype) # float32
2
常用数据类型:int8, int16, int32, int64, uint8, float16, float32, float64, bool 等。
# 2.2 数组属性与基本操作
# 2.2.1 查看属性
a = np.array([[1, 2, 3], [4, 5, 6]])
print("ndim:", a.ndim) # 维度数:2
print("shape:", a.shape) # 形状:(2, 3)
print("size:", a.size) # 元素总数:6
print("dtype:", a.dtype) # 数据类型:int64
print("itemsize:", a.itemsize) # 每个元素字节数:8(int64)
print("nbytes:", a.nbytes) # 总字节数:48
2
3
4
5
6
7
# 2.2.2 改变形状
reshape 返回一个新数组,不修改原数据:
a = np.arange(6) # [0 1 2 3 4 5]
print(a)
b = a.reshape(2, 3) # 变成2行3列
print(b)
# [[0 1 2]
# [3 4 5]]
# 使用 -1 自动推断维度
c = a.reshape(3, -1) # 3行,列自动计算为2
print(c)
# [[0 1]
# [2 3]
# [4 5]]
2
3
4
5
6
7
8
9
10
11
12
13
resize 直接修改原数组(注意与 ndarray.resize 区别)。
# 2.2.3 数组转置
a = np.array([[1, 2], [3, 4]])
print(a)
b = a.T # 或 a.transpose()
print(b)
# [[1 3]
# [2 4]]
2
3
4
5
6
# 2.3 索引与切片
NumPy的索引与 Python 列表类似,但支持多维切片。
# 2.3.1 一维数组索引与切片
a = np.array([0, 1, 2, 3, 4, 5])
print(a[2]) # 2
print(a[-1]) # 5
print(a[1:4]) # [1 2 3]
print(a[:3]) # [0 1 2]
print(a[::2]) # [0 2 4]
2
3
4
5
6
# 2.3.2 多维数组索引
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(a[0, 1]) # 第0行第1列:2
print(a[1]) # 第1行:[4 5 6]
print(a[:, 1]) # 所有行的第1列:[2 5 8]
print(a[0:2, 1:3]) # 前两行,第1~2列:[[2 3] [5 6]]
2
3
4
5
# 2.3.3 布尔索引
a = np.array([1, 2, 3, 4, 5])
mask = a > 2
print(mask) # [False False True True True]
print(a[mask]) # [3 4 5]
2
3
4
布尔索引常用于条件筛选。
# 2.3.4 花式索引(整数数组索引)
a = np.arange(10)**2
print(a)
indices = np.array([1, 3, 5])
print(a[indices]) # [1 9 25]
2
3
4
多维花式索引:
a = np.array([[1, 2], [3, 4], [5, 6]])
rows = np.array([0, 2])
cols = np.array([1, 0])
print(a[rows, cols]) # [2 5] 分别取 (0,1) 和 (2,0)
2
3
4
# 2.4 数组运算
NumPy支持逐元素运算(广播机制将在后面讲解)。
# 2.4.1 算术运算
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(a + b) # [5 7 9]
print(a - b) # [-3 -3 -3]
print(a * b) # [4 10 18]
print(a / b) # [0.25 0.4 0.5 ]
print(a ** 2) # [1 4 9]
2
3
4
5
6
7
8
# 2.4.2 比较运算
print(a > 2) # [False False True]
print(a == b) # [False False False]
2
# 2.4.3 通用函数(ufunc)
NumPy提供了许多对数组进行快速元素级运算的函数,称为通用函数。
a = np.array([1, 2, 3])
print(np.sqrt(a)) # [1. 1.41421356 1.73205081]
print(np.exp(a)) # [ 2.71828183 7.3890561 20.08553692]
print(np.sin(a)) # [0.84147098 0.90929743 0.14112001]
print(np.log(a)) # [0. 0.69314718 1.09861229]
2
3
4
5
# 2.5 广播机制
广播是NumPy处理不同形状数组之间运算的规则。简单来说,它会将较小的数组“扩展”成与较大数组相同的形状,使运算可行。
# 2.5.1 广播规则
- 如果两个数组的维度数不同,则维度较小的数组的形状会在其左边补1,直到维度数相同。
- 如果两个数组在某个维度上的大小不一致,但其中一个为1,则可以沿着该维度广播。
- 如果两个数组在任何维度上大小既不一致也不为1,则广播失败。
# 2.5.2 示例
a = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2,3)
b = np.array([10, 20, 30]) # shape (3,)
c = a + b
print(c)
# [[11 22 33]
# [14 25 36]]
2
3
4
5
6
这里 b 形状为 (3,),与 a 的第二个维度匹配,广播相当于将 b 沿第一个维度复制2份。
a = np.array([[1], [2], [3]]) # shape (3,1)
b = np.array([4, 5, 6]) # shape (3,)
"""
a 从 (3, 1) 广播到 (3, 3):沿着列方向复制 3 份,变成
[[1, 1, 1],
[2, 2, 2],
[3, 3, 3]]
b 从 (1, 3) 广播到 (3, 3):沿着行方向复制 3 份,变成
[[4, 5, 6],
[4, 5, 6],
[4, 5, 6]]
"""
c = a + b
print(c)
# [[5 6 7]
# [6 7 8]
# [7 8 9]]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
a 形状 (3,1),b 形状 (3,),补齐后 a (3,1),b (1,3),广播后均为 (3,3)。
# 2.6 线性代数操作
NumPy提供线性代数子模块 linalg。
# 2.6.1 矩阵乘法
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# 逐元素乘法
print(a * b)
# [[5 12]
# [21 32]]
# 矩阵乘法(点积)
c = np.dot(a, b) # 或 a @ b (Python 3.5+)
print(c)
# [[19 22]
# [43 50]]
2
3
4
5
6
7
8
9
10
11
12
13
# 2.6.2 其他常用操作
from numpy.linalg import inv, det, eig, solve
a = np.array([[1, 2], [3, 4]])
print(inv(a)) # 逆矩阵
print(det(a)) # 行列式:-2.0
print(eig(a)) # 特征值和特征向量
# 解线性方程组 Ax = b
A = np.array([[3, 1], [1, 2]])
b = np.array([9, 8])
x = solve(A, b)
print(x) # [2. 3.]
2
3
4
5
6
7
8
9
10
11
12
13
# 2.7 随机数生成
numpy.random 模块用于生成随机数。
# 设置种子以保证可重复性
np.random.seed(42)
# 生成[0,1)均匀分布的随机数
rand = np.random.rand(3, 2) # 3x2数组
print(rand)
# 生成标准正态分布随机数
randn = np.random.randn(3, 2)
print(randn)
# 生成指定范围内的随机整数
randint = np.random.randint(0, 10, size=(2, 3))
print(randint)
# 从给定数组中随机选择
choice = np.random.choice([1, 2, 3], size=5, p=[0.2, 0.5, 0.3])
print(choice)
# 打乱数组顺序
arr = np.arange(10)
np.random.shuffle(arr)
print(arr)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2.8 数组操作进阶
# 2.8.1 合并数组
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
# 垂直合并(增加行)
vstack = np.vstack((a, b))
print(vstack)
# [[1 2]
# [3 4]
# [5 6]]
# 水平合并(增加列)
c = np.array([[5], [6]])
hstack = np.hstack((a, c))
print(hstack)
# [[1 2 5]
# [3 4 6]]
# 沿新轴合并(增加维度)
d = np.array([1, 2, 3])
e = np.array([4, 5, 6])
stack = np.stack((d, e), axis=1) # 作为列堆叠
print(stack)
# [[1 4]
# [2 5]
# [3 6]]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 2.8.2 分割数组
a = np.arange(12).reshape(3, 4)
print(a)
# 水平分割(按列)
hsplit = np.hsplit(a, 2) # 分割成2个数组
print(hsplit[0])
# [[0 1]
# [4 5]
# [8 9]]
print(hsplit[1])
# 垂直分割(按行)
vsplit = np.vsplit(a, 3)
print(vsplit[0])
# [[0 1 2 3]]
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.8.3 添加/删除维度
a = np.array([1, 2, 3]) # shape (3,)
# a[:, np.newaxis] 的含义是:保持原数组的所有元素(:),但在列的位置(第二个轴)增加一个新维度
# b 的形状变为 (3, 1),即 3 行 1 列的二维数组(列向量)
b = a[:, np.newaxis] # 变成列向量 (3,1)
c = a[np.newaxis, :] # 变成行向量 (1,3), [[1 2 3]],注意不是[1 2 3]
print(a)
print(b)
print(c)
# 去除长度为1的维度
d = np.squeeze(b) # 回到 (3,)
print(d)
2
3
4
5
6
7
8
9
10
11
# 2.9 统计函数
NumPy提供多种统计计算函数。
a = np.array([[1, 2, 3], [4, 5, 6]])
print(np.sum(a)) # 所有元素和:21
print(np.sum(a, axis=0)) # 按列求和:[5 7 9]
print(np.sum(a, axis=1)) # 按行求和:[6 15]
print(np.mean(a)) # 平均值:3.5
print(np.median(a)) # 中位数:3.5
print(np.std(a)) # 标准差:1.7078
print(np.var(a)) # 方差:2.9167
print(np.min(a)) # 最小值:1
print(np.max(a, axis=1)) # 每行最大值:[3 6]
print(np.argmin(a)) # 最小值索引(扁平化后):0
print(np.argmax(a, axis=0)) # 每列最大值索引:[1 1 1]
# 累积和
print(np.cumsum(a)) # [1 3 6 10 15 21]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.10 文件输入输出
NumPy可以方便地读写数组到文件。
# 2.10.1 二进制格式(.npy)
a = np.array([1, 2, 3])
np.save('my_array.npy', a) # 保存单个数组
loaded = np.load('my_array.npy')
print(loaded)
2
3
4
保存多个数组可使用 np.savez(压缩)或 np.savez_compressed。
# 2.10.2 文本格式
a = np.array([[1, 2, 3], [4, 5, 6]])
np.savetxt('my_array.txt', a, delimiter=',', fmt='%d')
loaded = np.loadtxt('my_array.txt', delimiter=',')
print(loaded)
2
3
4
# 小结
NumPy 提供了高性能的多维数组对象以及丰富的操作函数,是后续学习 Pandas、SciPy 等库的基础。熟练掌握 NumPy的索引、切片、广播以及常用函数,能够显著提高数据处理效率。
# 3. Pandas 详解
Pandas 是 Python 数据分析的核心库,它基于 NumPy 构建,提供了两种主要的数据结构:Series(一维)和 DataFrame(二维),并配备了丰富的数据处理函数。Pandas 让数据清洗、转换、分析变得简单高效。
# 3.1 Pandas 数据结构
# 3.1.1 Series
Series 是一种一维的带标签数组,可以存储任意数据类型(整数、浮点数、字符串、Python 对象等)。它的标签统称为索引。
一个 Series 包含两个核心部分:
- 数据(values):一维数组,可以是整数、浮点数、字符串等各种类型。
- 索引(index):与数据一一对应的标签。如果没有显式指定索引,Pandas 会自动生成从 0 开始的整数索引。
创建 Series:
import pandas as pd
import numpy as np
# 从列表创建
s1 = pd.Series([1, 3, 5, np.nan, 6])
print(s1)
# 0 1.0
# 1 3.0
# 2 5.0
# 3 NaN
# 4 6.0
# dtype: float64
# 指定索引
s2 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
print(s2)
# a 1
# b 2
# c 3
# dtype: int64
# 从字典创建(键成为索引)
s3 = pd.Series({'a': 1, 'b': 2, 'c': 3})
print(s3)
# a 1
# b 2
# c 3
# dtype: int64
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
Series 的常用属性:
print(s2.values) # [1 2 3]
print(s2.index) # Index(['a', 'b', 'c'], dtype='str')
print(s2.dtype) # int64
print(s2.shape) # (3,)
2
3
4
# 3.1.2 DataFrame
DataFrame 是一个二维的、带行列标签的数据结构。你可以把它想象成一个 Excel 表格或 SQL 表。
一个 DataFrame 包含三个主要部分:
- 数据(values):二维数组(类似 NumPy 的二维数组),存储表格中的数据。
- 行索引(index):每一行的标签,可以是整数、字符串等。
- 列名(columns):每一列的标签。
创建 DataFrame:
# 从字典创建(键为列名,值为列表或数组)
data = {
'name': ['Alice', 'Bob', 'Charlie'],
'age': [25, 30, 35],
'city': ['New York', 'Paris', 'London']
}
df = pd.DataFrame(data)
print(df)
# name age city
# 0 Alice 25 New York
# 1 Bob 30 Paris
# 2 Charlie 35 London
# 从二维 NumPy 数组创建,同时指定列名和索引
arr = np.random.randn(3, 4)
df2 = pd.DataFrame(arr, columns=['A', 'B', 'C', 'D'], index=['x', 'y', 'z'])
print(df2)
# A B C D
# x 0.123456 -0.123456 1.234567 -0.987654
# y 0.456789 -0.456789 1.345678 -0.876543
# z 0.789012 -0.789012 1.456789 -0.765432
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
常用属性:
print(df.shape) # (3, 3)
print(df.columns) # Index(['name', 'age', 'city'], dtype='str')
print(df.index) # RangeIndex(start=0, stop=3, step=1)
print(df.dtypes) # 每列数据类型
print(df.info()) # 查看数据信息,包括非空计数
2
3
4
5
# 3.2 数据导入与导出
Pandas 支持多种文件格式的读写,最常用的是 CSV、Excel、JSON 等。
# 3.2.1 写入文件
为了读取文件,我们先生成对于的文件并写入内容。
import pandas as pd
import numpy as np
import json
import pymysql
from datetime import datetime
# 设置随机种子以便结果可复现
np.random.seed(42)
# -------------------- 1. 生成 data.csv --------------------
csv_data = pd.DataFrame({
'col1': [f'ID_{i}' for i in range(1, 6)], # 字符串类型
'col2': np.random.randint(10, 100, 5), # 整数
'date': pd.date_range('2023-01-01', periods=5, freq='D') # 日期
})
csv_data.to_csv('data.csv', index=False, encoding='utf-8')
print("已生成 data.csv")
# -------------------- 2. 生成 data.xlsx --------------------
excel_data = pd.DataFrame({
'A': [1, 2, 3, 4, 5],
'B': ['apple', 'banana', 'cherry', 'date', 'elderberry'],
'C': [1.1, 2.2, 3.3, 4.4, 5.5]
})
# 需要安装 openpyxl
excel_data.to_excel('data.xlsx', sheet_name='Sheet1', index=False)
print("已生成 data.xlsx")
# -------------------- 3. 生成 data.json --------------------
json_data = [
{"name": "Alice", "age": 25, "city": "New York"},
{"name": "Bob", "age": 30, "city": "Paris"},
{"name": "Charlie", "age": 35, "city": "London"}
]
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(json_data, f, indent=2)
print("已生成 data.json")
# -------------------- 4. 生成 MySQL 数据脚本 data.sql --------------------
sql_content = """-- 创建数据库(如果需要)
-- CREATE DATABASE IF NOT EXISTS testdb;
-- USE testdb;
-- 创建表(注意:表名避免使用关键字,这里用 users)
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` INT PRIMARY KEY,
`name` VARCHAR(50),
`age` INT,
`salary` DECIMAL(10,2)
);
-- 插入数据
INSERT INTO `users` (`id`, `name`, `age`, `salary`) VALUES
(1, 'John', 28, 50000),
(2, 'Jane', 32, 62000),
(3, 'Mike', 41, 75000),
(4, 'Emily', 26, 48000);
"""
with open('data.sql', 'w', encoding='utf-8') as f:
f.write(sql_content)
print("已生成 data.sql (MySQL 脚本)")
print("\n所有示例文件已生成。")
print("对于 MySQL,请先执行 data.sql 文件将数据导入数据库,然后使用 pymysql 连接读取。")
print("-" * 50)
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
3.2.2 读取文件
import pandas as pd
import pymysql
# 读取 CSV
df_csv = pd.read_csv('data.csv', sep=',', header=0, index_col=0,
usecols=['col1', 'col2'], dtype={'col1': str},
parse_dates=['date'], encoding='utf-8')
print("CSV 读取结果:")
print(df_csv.head())
# 读取 Excel
df_excel = pd.read_excel('data.xlsx', sheet_name='Sheet1')
print("\nExcel 读取结果:")
print(df_excel.head())
# 读取 JSON
df_json = pd.read_json('data.json')
print("\nJSON 读取结果:")
print(df_json.head())
# 读取 MySQL(请先修改连接信息)
conn = pymysql.connect(host='localhost', user='root', password='123456', database='testdb')
df_sql = pd.read_sql_query("SELECT * FROM users", conn)
conn.close()
print("\nMySQL 读取结果:")
print(df_sql.head())
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
# 3.3 数据查看与基本信息
df = pd.read_csv('hep.csv', encoding='utf-8')
"""
hep.csv的内容如下:
id,name,age,phone,email,city
1,Alice Johnson,28,13811223344,alice.johnson@gmail.com,Paris
2,Bob Smith,34,13999887766,bob.smith@163.com,London
3,Carol Davis,45,15933445566,carol.davis@qq.com,New York
4,David Brown,52,15877665544,david.brown@126.com,Tokyo
5,Eva Wilson,23,17788990011,eva.wilson@outlook.com,Beijing
6,Frank Miller,38,18822334455,frank.miller@hotmail.com,Sydney
7,Grace Lee,41,13644556677,grace.lee@yahoo.com,Moscow
8,Henry Taylor,29,18977889900,henry.taylor@sina.com,Rome
9,Ivy Anderson,33,15233448899,ivy.anderson@sohu.com,Berlin
10,Jack Thomas,47,18755667788,jack.thomas@gmail.com,Madrid
11,Karen Moore,26,18233445566,karen.moore@qq.com,Cairo
12,Larry Jackson,55,18399887766,larry.jackson@163.com,Rio
13,Megan White,31,18511223344,megan.white@126.com,Toronto
14,Nick Harris,44,18655667788,nick.harris@outlook.com,Dubai
"""
# 查看前几行
print(df.head()) # 默认前5行
print(df.head(10))
# 查看后几行
print(df.tail())
# 随机查看几行
print(df.sample(5))
# 查看概览信息
print(df.info())
# 描述性统计(数值列)
print(df.describe())
# 查看各列的数据类型
print(df.dtypes)
# 查看数据维度
print(df.shape)
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
# 3.4 数据选择与索引
Pandas 提供了多种选择数据的方法,包括基于标签的 .loc 和基于整数位置的 .iloc,以及直接使用列名和切片。
# 3.4.1 列选择
# 选择单列 -> Series
names = df['name']
print(type(names)) # <class 'pandas.core.series.Series'>
# 选择多列 -> DataFrame
subset = df[['name', 'age']]
# 通过属性访问列(列名必须是有效标识符)
# df.name # 不推荐,容易与现有方法冲突
2
3
4
5
6
7
8
9
# 3.4.2 行选择
使用 [] 切片(按行位置):
# 前两行
print(df[:2])
# 第1行到第3行(不含第3行)
print(df[1:3])
2
3
4
5
使用 .loc[](基于标签):
# 假设索引是标签,例如设置 name 列为索引
df_indexed = df.set_index('name')
print(df_indexed.loc['Alice Johnson']) # 选择单行 -> Series
print(df_indexed.loc[['Alice Johnson', 'Megan White']]) # 选择多行 -> DataFrame
# 同时选择行和列
print(df_indexed.loc['Alice Johnson', 'age']) # 单个值
print(df_indexed.loc['Alice Johnson', ['age', 'phone']])
2
3
4
5
6
7
8
使用 .iloc[](基于整数位置):
# 选择第0行
print(df.iloc[0])
# 选择第0行和第2行
print(df.iloc[[0, 2]])
# 选择行和列(前两行,前两列)
print(df.iloc[0:2, 0:2])
2
3
4
5
6
7
8
# 3.4.3 布尔索引
根据条件筛选行。
# 年龄大于25的人
print(df[df['age'] > 25])
# 多个条件
print(df[(df['age'] > 25) & (df['city'] == 'Paris')]) # 与
print(df[(df['age'] > 30) | (df['city'] == 'New York')]) # 或
# 使用 isin 进行成员判断
print(df[df['city'].isin(['Paris', 'London'])])
2
3
4
5
6
7
8
9
# 3.5 数据处理
# 3.5.1 缺失值处理
现实数据中经常存在缺失值,Pandas 使用 NaN(Not a Number)表示。
# 创建含缺失值的 DataFrame
df_missing = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, 7, 8],
'C': [9, 10, 11, 12]
})
# 检测缺失值
print(df_missing.isnull())
print(df_missing.isna().sum()) # 每列缺失数量
# 删除缺失值
df_dropna = df_missing.dropna() # 删除任何包含NaN的行
df_dropna_col = df_missing.dropna(axis=1) # 删除任何包含NaN的列
df_dropna_thresh = df_missing.dropna(thresh=2) # 至少保留2个非空值的行
# 填充缺失值
df_fill = df_missing.fillna(0) # 用0填充
df_fill_mean = df_missing.fillna(df_missing.mean()) # 用该列均值填充
df_ffill = df_missing.ffill() # 前向填充
df_bfill = df_missing.bfill() # 后向填充
# 插值
df_interpolate = df_missing.interpolate() # 线性插值
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3.5.2 重复值处理
# 创建含重复行的 DataFrame
df_dup = pd.DataFrame({
'A': [1, 1, 2, 2, 3],
'B': ['x', 'x', 'y', 'z', 'z']
})
print(df_dup)
# 检测重复行
print(df_dup.duplicated()) # 返回布尔Series,标记重复行(除第一次外)
# 删除重复行
df_deduplicated = df_dup.drop_duplicates() # 保留第一次出现
df_deduplicated_last = df_dup.drop_duplicates(keep='last') # 保留最后一次出现
df_deduplicated_subset = df_dup.drop_duplicates(subset=['A']) # 根据特定列去重
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.5.3 数据类型转换
import pandas as pd
# 读取 CSV 文件
df = pd.read_csv('hep.csv', encoding='utf-8')
# 1. 查看原始数据类型
print("原始数据类型:")
print(df.dtypes)
# 2. 转换类型示例
# 将 age 从整数转换为浮点数(演示数值类型转换)
df['age'] = df['age'].astype(float)
# 将 id 从整数转换为字符串(ID 通常作为标签,不参与计算)
df['id'] = df['id'].astype(str)
# 将 city 转换为分类类型,节省内存并便于分组分析
df['city'] = df['city'].astype('category')
# 3. 再次查看转换后的数据类型
print("\n转换后的数据类型:")
print(df.dtypes)
# 4. 如果需要日期转换,可以添加一列
# 例如:添加当前日期作为注册日期
# df['reg_date'] = pd.to_datetime('2025-01-01')
# 或者从某列解析日期
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
# 3.5.4 数据替换
# 替换特定值
df.replace(0, 100) # 将0替换为100
df.replace({'age': {25: 26, 30: 31}}) # 在df的age列中,将所有值为25的单元格替换为26,将所有值为30的单元格替换为31
# 使用条件替换
df.loc[df['age'] > 30, 'age'] = 30
2
3
4
5
6
# 3.5.5 数据排序
# 按值排序
df_sorted = df.sort_values(by='age', ascending=False) # 降序
print(df_sorted)
df_sorted_multi = df.sort_values(by=['age', 'name'], ascending=[True, False])
print(df_sorted_multi)
# 按索引排序
df_sorted_index = df.sort_index(ascending=False)
print(df_sorted_index)
2
3
4
5
6
7
8
# 3.6 数据变换
# 3.6.1 添加新列
# 直接赋值
df['age_plus_10'] = df['age'] + 10
#print(df)
# 根据现有列计算
df['name_length'] = df['name'].apply(len)
#print(df)
# 使用 assign 方法(返回新 DataFrame,不修改原数据)
df_new = df.assign(age_squared=lambda x: x['age'] ** 2)
#print(df_new)
2
3
4
5
6
7
8
9
# 3.6.2 删除列/行
# 删除列
df_dropped = df.drop('age_plus_10', axis=1) # 或 axis='columns'
df.drop('age_plus_10', axis=1, inplace=True) # 原地删除
# 删除行
df_dropped_row = df.drop([0, 2]) # 删除索引为0和2的行
2
3
4
5
6
# 3.6.3 应用函数
apply(): 对行或列应用函数。map(): 对 Series 每个元素应用函数(用于 Series)。applymap(): 对 DataFrame 每个元素应用函数(用于 DataFrame)。
import pandas as pd
import numpy as np
# 读取数据
df = pd.read_csv('hep.csv', encoding='utf-8')
# 1. apply 作用于列
df['age_sqrt'] = df['age'].apply(np.sqrt) # 对 age 列开平方
df['age_category'] = df['age'].apply(lambda x: 'young' if x < 30 else 'old')
print(df[['name', 'age', 'age_sqrt', 'age_category']]) # 查看部分列
# 2. apply 作用于行 (axis=1)
df['description'] = df.apply(lambda row: f"{row['name']} is {row['age']} years old", axis=1)
print(df[['name', 'description']])
# 3. map 用于 Series(常与字典配合)
city_map = {'New York': 'NYC', 'Paris': 'PAR', 'London': 'LDN'}
df['city_code'] = df['city'].map(city_map)
print(df[['city', 'city_code']])
# 4. 选取数值列并标准化(向量化操作)
df_numeric = df.select_dtypes(include=[np.number]) # 选取整数/浮点数列(如 id, age)
df_normalized = (df_numeric - df_numeric.mean()) / df_numeric.std() # 直接向量化计算
print("标准化后的数值列:")
print(df_normalized)
# 如果需要将标准化后的数据合并回原 DataFrame,可以用 concat
df_final = pd.concat([df, df_normalized.add_suffix('_norm')], axis=1)
print(df_final.head())
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
# 3.6.4 重命名轴
# 重命名列
df.rename(columns={'age': 'Age', 'city': 'City'}, inplace=True)
# 重命名索引
df.rename(index={0: 'first', 1: 'second'}, inplace=True)
# 设置某列为索引
df.set_index('name', inplace=True)
# 重置索引(将索引变为普通列)
df.reset_index(inplace=True)
2
3
4
5
6
7
8
9
10
11
# 3.7 分组聚合
分组聚合是数据分析中非常常见的操作,类似于 SQL 的 GROUP BY。
# 3.7.1 groupby 基础
# 创建示例数据
df_sales = pd.DataFrame({
'store': ['A', 'A', 'B', 'B', 'C', 'C'],
'product': ['apple', 'banana', 'apple', 'banana', 'apple', 'banana'],
'sales': [100, 150, 200, 250, 300, 350],
'date': pd.date_range('2023-01-01', periods=6, freq='D')
})
print(df_sales)
# 按单个列分组
grouped = df_sales.groupby('store')
# 查看分组对象
print(grouped) # <pandas.core.groupby.generic.DataFrameGroupBy object>
2
3
4
5
6
7
8
9
10
11
12
13
# 3.7.2 聚合函数
可以使用内置聚合函数或自定义聚合。
# 对分组后的数值列求和
sum_sales = grouped['sales'].sum()
print(sum_sales)
# store
# A 250
# B 450
# C 650
# Name: sales, dtype: int64
# 多个聚合函数
agg_result = grouped['sales'].agg(['sum', 'mean', 'std'])
print(agg_result)
# sum mean std
# store
# A 250 125 35.355339
# B 450 225 35.355339
# C 650 325 35.355339
# 对不同列应用不同聚合
# agg_dict 是一个字典,键为列名,值为要应用的聚合函数(可以是单个函数名或函数名列表)
agg_dict = {
'sales': ['sum', 'mean'], #对 sales 列计算总和(sum)和平均值(mean)
'product': 'count' # 对 product 列计算计数(count),即每个商店有多少个产品记录
}
print(agg_dict)
result = grouped.agg(agg_dict)
print(result)
# sales product
# sum mean count
# store
# A 250 125.0 2
# B 450 225.0 2
# C 650 325.0 2
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
# 3.7.3 转换与过滤
转换(transform):返回与原始数据相同形状的结果。
# 计算每个商店销售额占总销售额的比例
# df_sales.groupby('store'):按 store 列分组,将同一商店的所有行归为一组
# ['sales']:选取分组后的 sales 列(销售额)
# .transform('sum'):对每组内的 sales 列应用求和('sum'),但 返回一个与原 DataFrame 行数相同、索引对齐的 Series。也就是说,对于每一行,store_total 的值等于该行所属商店的销售额总和
df_sales['store_total'] = df_sales.groupby('store')['sales'].transform('sum')
# 直接用每行的 sales 值除以该行的 store_total(即所属商店的总销售额),得到该笔销售在商店内的占比,结果存入新列 store_ratio
df_sales['store_ratio'] = df_sales['sales'] / df_sales['store_total']
print(df_sales)
2
3
4
5
6
7
8
过滤(filter):根据分组条件过滤整个组。
# 保留总销售额大于 400 的商店
# df_sales.groupby('store'):按 store 列(商店名称)对数据进行分组,每个商店的所有行形成一个组
# .filter(...):对每个组应用一个判断函数,函数返回 True 的组会被保留,返回 False 的组会被过滤掉(即该组所有行都不出现在结果中)
# lambda x: x['sales'].sum() > 400,它计算该组 sales 列的总和,如果总和大于 400,则返回 True,否则返回 False
filtered = df_sales.groupby('store').filter(lambda x: x['sales'].sum() > 400)
print(filtered)
2
3
4
5
6
# 3.7.4 多级索引与分组
分组可以按多个列进行,产生层次化索引。
# 按 store 和 product 分组
grouped_multi = df_sales.groupby(['store', 'product'])
result_multi = grouped_multi['sales'].sum()
print(result_multi)
# store product
# A apple 100
# banana 150
# B apple 200
# banana 250
# C apple 300
# banana 350
# Name: sales, dtype: int64
# 可以 unstack 转换为透视表形式
pivot = result_multi.unstack()
print(pivot)
# product apple banana
# store
# A 100 150
# B 200 250
# C 300 350
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.8 合并与连接
Pandas 提供了多种合并数据的方式:concat、merge 和 join。
# 3.8.1 concat
concat 用于沿轴方向拼接多个 DataFrame。
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]})
# 垂直拼接(增加行)
df_concat = pd.concat([df1, df2], axis=0, ignore_index=True)
print(df_concat)
# A B
# 0 1 3
# 1 2 4
# 2 5 7
# 3 6 8
# 水平拼接(增加列)
df3 = pd.DataFrame({'C': [9, 10], 'D': [11, 12]})
df_concat_h = pd.concat([df1, df3], axis=1)
print(df_concat_h)
# A B C D
# 0 1 3 9 11
# 1 2 4 10 12
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3.8.2 merge
merge 类似于 SQL 的 JOIN 操作,根据一个或多个键将行连接起来。
import pandas as pd
# 左表:键 K0, K1, K2, K3
left = pd.DataFrame({
'key': ['K0', 'K1', 'K2', 'K3'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']
})
# 右表:键 K0, K1, K4(缺少 K2,K3,新增 K4)
right = pd.DataFrame({
'key': ['K0', 'K1', 'K4'],
'C': ['C0', 'C1', 'C4'],
'D': ['D0', 'D1', 'D4']
})
print("左表:")
print(left)
print("\n右表:")
print(right)
# 1. 内连接:只保留两个表都有的键(K0, K1)
inner = pd.merge(left, right, on='key', how='inner')
print("\n内连接结果(交集):")
print(inner)
# 2. 左连接:保留左表所有行,右表匹配不上的填充 NaN(K2, K3 的 C/D 为 NaN)
left_join = pd.merge(left, right, on='key', how='left')
print("\n左连接结果(左表全保留):")
print(left_join)
# 3. 右连接:保留右表所有行,左表匹配不上的填充 NaN(K4 的 A/B 为 NaN)
right_join = pd.merge(left, right, on='key', how='right')
print("\n右连接结果(右表全保留):")
print(right_join)
# 4. 外连接:保留所有键(K0,K1,K2,K3,K4),缺失部分填充 NaN
outer = pd.merge(left, right, on='key', how='outer')
print("\n外连接结果(并集):")
print(outer)
# 5. 连接键名称不同的情况
left_renamed = left.rename(columns={'key': 'left_key'})
right_renamed = right.rename(columns={'key': 'right_key'})
merged_diff = pd.merge(left_renamed, right_renamed,
left_on='left_key', right_on='right_key',
how='inner') # 这里以 inner 为例
print("\n连接键名称不同时的合并(内连接):")
print(merged_diff)
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
# 3.8.3 join
join 是基于索引的合并,实际是 merge 的一种便捷方式。
left = pd.DataFrame({'A': [1, 2]}, index=['a', 'b'])
right = pd.DataFrame({'B': [3, 4]}, index=['a', 'c'])
# 默认左连接
joined = left.join(right, how='left')
print(joined)
# A B
# a 1 3.0
# b 2 NaN
# 外连接
joined_outer = left.join(right, how='outer')
2
3
4
5
6
7
8
9
10
11
12
# 3.9 时间序列
Pandas 对时间序列数据提供了强大的支持,包括日期范围生成、重采样、时间偏移等。
import pandas as pd
import numpy as np
# 创建示例销售数据(包含日期列)
np.random.seed(42)
date_rng = pd.date_range(start='2023-01-01', end='2023-12-31', freq='D')
df_sales = pd.DataFrame({
'date': date_rng,
'sales': np.random.randint(100, 500, size=len(date_rng)),
'store': np.random.choice(['A', 'B', 'C'], size=len(date_rng))
})
# 将日期列设置为索引
df_sales['date'] = pd.to_datetime(df_sales['date'])
df_sales = df_sales.set_index('date')
# 创建时间序列
ts = pd.Series(np.random.randn(365),
index=pd.date_range('2023-01-01', periods=365, freq='D'))
# 降采样:日 -> 月(使用新频率 'ME')
monthly_mean = ts.resample('ME').mean()
print("月均值:")
print(monthly_mean.head())
# 升采样(日->小时)并填充(使用小写 'h')
hourly = ts.resample('h').ffill()
print("\n升采样到小时后的前几个值:")
print(hourly.head(10))
# 多个聚合函数
monthly_stats = ts.resample('ME').agg(['mean', 'std', 'count'])
print("\n每月统计:")
print(monthly_stats.head())
# 按商店分组后重采样
store_monthly = df_sales.groupby('store').resample('ME')['sales'].mean()
print("\n各商店月均销售额:")
print(store_monthly)
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
# 3.10 数据透视表与交叉表
Pandas 的 pivot_table 和 crosstab 可以快速生成类似 Excel 透视表的汇总数据。
import pandas as pd
import numpy as np
# 设置随机种子以便结果可复现
np.random.seed(42)
# 创建示例数据
df_sales = pd.DataFrame({
'store': ['A', 'A', 'B', 'B', 'C', 'C'] * 2,
'product': ['apple', 'banana', 'apple', 'banana', 'apple', 'banana'] * 2,
'sales': np.random.randint(100, 500, 12),
'quarter': ['Q1'] * 6 + ['Q2'] * 6
})
print("原始数据:")
print(df_sales.head(10))
print()
# 1. 基本数据透视表:按商店和产品汇总销售额
pivot = pd.pivot_table(
df_sales,
values='sales',
index='store',
columns='product',
aggfunc='sum',
fill_value=0,
margins=True, # 添加行列总计(可选)
margins_name='总计'
)
print("基本数据透视表(商店 x 产品):")
print(pivot)
print()
# 2. 多级索引和多聚合函数
pivot_multi = pd.pivot_table(
df_sales,
values='sales',
index=['store', 'quarter'], # 行索引为商店和季度
columns='product',
aggfunc=['sum', 'mean'], # 同时计算总和与平均值
fill_value=0
)
print("多级索引与多聚合函数:")
print(pivot_multi)
print()
# 3. 交叉表(crosstab):也可以用于聚合,但通常用于频次统计
# 这里演示按商店和季度对销售额求和
crosstab = pd.crosstab(
index=df_sales['store'],
columns=df_sales['quarter'],
values=df_sales['sales'],
aggfunc='sum',
margins=True,
margins_name='总计'
)
print("交叉表(商店 x 季度):")
print(crosstab)
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
# 3.11 性能优化与注意事项
- 使用向量化操作:尽量避免逐行循环,利用 Pandas 和
NumPy的内置向量化函数。 - 选择合适的数据类型:对于分类数据使用
category类型可节省内存;对于数值数据,如果范围合适,可使用int8等更小的类型。 - 使用
inplace谨慎:inplace=True可能会引起链式赋值警告,且不易调试,通常建议赋值给新变量。 - 链式索引的陷阱:避免使用
df[df.a > 0]['b']这种链式索引,它可能返回视图或副本,导致警告或错误。应使用.loc:df.loc[df.a > 0, 'b']。 - 使用
query方法:对于复杂条件,df.query('a > 0 & b < 5')可读性更好。 - 处理大数据集:当数据量过大无法装入内存时,可以考虑分块读取(
pd.read_csv(..., chunksize=...))或使用 Dask 等工具。
# 小结
Pandas 是数据处理的核心工具,掌握其各种操作能够高效地进行数据清洗、转换、聚合和分析。本部分涵盖了从基础数据结构到高级数据操作的方方面面,配合代码示例,希望读者能够熟练掌握。
# 4. Python 数据可视化
数据可视化是数据分析中至关重要的一环,它能帮助我们直观地理解数据分布、趋势和关系。Python 拥有丰富的数据可视化库,本部分将详细介绍其中最常用的四个:Matplotlib、Pandas 内置绘图、Seaborn 和 Pyecharts。前三者主要用于静态图表,而 Pyecharts 则专注于交互式可视化。我们将从基础开始,逐步深入,并提供大量代码示例,所有示例均在 Jupyter Notebook 中运行。
# 4.1 Matplotlib 基础
Matplotlib 是 Python 中最基础、最强大的绘图库,它提供了高度自定义的绘图接口。虽然语法相对底层,但它是许多高级绘图库(如 Seaborn)的基础。
# 4.1.1 基本绘图流程
Matplotlib 的基本使用流程通常包括:创建图形和坐标轴、调用绘图函数、设置标签和标题、显示或保存图像。
import matplotlib.pyplot as plt
import numpy as np
# 准备数据
# 在0到10之间均匀生成100个点(包含端点),作为横坐标x
x = np.linspace(0, 10, 100)
y = np.sin(x)
# 创建图形和坐标轴
plt.figure(figsize=(8, 5)) # 可选,设置图形大小
# 绘图
plt.plot(x, y, label='sin(x)')
# 添加标签和标题
plt.xlabel('x')
plt.ylabel('sin(x)')
plt.title('正弦函数曲线')
plt.legend()
# 显示图形
plt.show()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4.1.2 Figure 和 Axes
Matplotlib 的核心概念是 Figure(画布)和 Axes(坐标系)。一个 Figure 可以包含多个 Axes(子图)。通常我们使用 plt.subplots() 创建 Figure 和 Axes 对象,然后调用 Axes 的方法进行绘图。
fig, ax = plt.subplots(figsize=(8, 5)) # 创建一个画布和一个坐标系
ax.plot(x, y)
ax.set_xlabel('x')
ax.set_ylabel('sin(x)')
ax.set_title('正弦函数')
ax.legend(['sin(x)'])
plt.show()
2
3
4
5
6
7
使用 Axes 对象的方法更加面向对象,适合复杂布局。
# 4.1.3 常用图表类型
折线图
x = np.arange(0, 10, 0.1)
y1 = np.sin(x)
y2 = np.cos(x)
plt.plot(x, y1, label='sin')
plt.plot(x, y2, label='cos')
plt.legend()
plt.title('折线图示例')
plt.show()
2
3
4
5
6
7
8
9
散点图
np.random.seed(42)
x = np.random.randn(100)
y = 2 * x + np.random.randn(100) * 0.5
plt.scatter(x, y, alpha=0.6, c='red', edgecolors='black')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('散点图示例')
plt.show()
2
3
4
5
6
7
8
9
柱状图
categories = ['A', 'B', 'C', 'D']
values = [23, 45, 56, 78]
plt.bar(categories, values, color=['blue', 'orange', 'green', 'red'])
plt.xlabel('类别')
plt.ylabel('值')
plt.title('柱状图示例')
plt.show()
2
3
4
5
6
7
8
直方图
data = np.random.randn(1000)
plt.hist(data, bins=30, edgecolor='black', alpha=0.7)
plt.xlabel('值')
plt.ylabel('频数')
plt.title('直方图示例')
plt.show()
2
3
4
5
6
饼图
sizes = [25, 35, 20, 20]
labels = ['苹果', '香蕉', '橘子', '葡萄']
explode = (0.1, 0, 0, 0) # 突出显示第一块
plt.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', startangle=90)
plt.axis('equal') # 保证饼图是圆形
plt.title('饼图示例')
plt.show()
2
3
4
5
6
7
8
# 4.1.4 自定义样式
Matplotlib 提供了丰富的自定义选项,包括颜色、线型、标记、标签、标题、图例等。
颜色和线型
x = np.linspace(0, 5, 50)
y1 = x
y2 = x**2
y3 = x**3
plt.plot(x, y1, color='red', linestyle='-', linewidth=2, label='线性')
plt.plot(x, y2, color='#00FF00', linestyle='--', marker='o', markersize=5, label='平方')
plt.plot(x, y3, color=(0.2, 0.4, 0.6), linestyle='-.', marker='^', label='立方')
plt.legend()
plt.show()
2
3
4
5
6
7
8
9
10
文本与注释
plt.plot(x, np.sin(x))
plt.text(2, 0.5, '局部最大值', fontsize=12, bbox=dict(facecolor='yellow', alpha=0.5))
plt.annotate('峰值', xy=(np.pi/2, 1), xytext=(np.pi/2+1, 0.8),
arrowprops=dict(facecolor='black', shrink=0.05))
plt.show()
2
3
4
5
坐标轴设置
plt.plot(x, np.sin(x))
plt.xlim(0, 2*np.pi) # 设置x轴范围
plt.ylim(-1.5, 1.5) # 设置y轴范围
plt.xticks([0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi],
['0', 'π/2', 'π', '3π/2', '2π'])
plt.yticks([-1, 0, 1])
plt.grid(True, linestyle='--', alpha=0.5)
plt.show()
2
3
4
5
6
7
8
# 4.1.5 子图布局
使用 subplots 可以创建多个子图,并通过 axs 索引访问每个子图。
fig, axs = plt.subplots(2, 2, figsize=(10, 8)) # 2行2列
x = np.linspace(0, 2*np.pi, 100)
axs[0, 0].plot(x, np.sin(x))
axs[0, 0].set_title('sin')
axs[0, 1].plot(x, np.cos(x))
axs[0, 1].set_title('cos')
axs[1, 0].plot(x, np.tan(x))
axs[1, 0].set_ylim(-5, 5)
axs[1, 0].set_title('tan')
axs[1, 1].plot(x, np.exp(-x) * np.sin(x))
axs[1, 1].set_title('阻尼振荡')
plt.tight_layout() # 自动调整子图间距
plt.show()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.2 Pandas 内置绘图
Pandas 的 Series 和 DataFrame 对象提供了 plot() 方法,它基于 Matplotlib 实现,可以快速绘制常见图表,特别适合数据探索阶段。
# 4.2.1 Series 和 DataFrame 的 plot 方法
plot() 方法的参数与 Matplotlib 类似,但更加简洁。
import pandas as pd
# 创建示例 DataFrame
# np.random.randn(100)生成100个服从标准正态分布的随机数,.cumsum()计算这些随机数的累积和,生成一个随机游走序列
# index=pd.date_range('2026-03-01', periods=100, freq='D')创建一个日期索引,从2026年3月1日开始,连续100天,频率为每日
df = pd.DataFrame({
'A': np.random.randn(100).cumsum(),
'B': np.random.randn(100).cumsum(),
'C': np.random.randn(100).cumsum()
}, index=pd.date_range('2026-03-01', periods=100, freq='D'))
# 默认折线图
df.plot(figsize=(10, 6))
plt.title('Pandas 默认折线图')
plt.show()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4.2.2 常用图表快速绘制
通过 kind 参数指定图表类型,如 'line'、'bar'、'barh'、'hist'、'box'、'kde'、'area'、'scatter'、'hexbin' 等。
柱状图
df_bar = pd.DataFrame({
'group': ['A', 'B', 'C', 'D'],
'values': [23, 45, 56, 78]
}).set_index('group')
df_bar.plot(kind='bar', legend=False)
plt.title('Pandas 柱状图')
plt.show()
2
3
4
5
6
7
8
直方图
# df[['A', 'B']]:从原始 DataFrame df 中选取列A和B,形成一个新的DataFrame
# kind='hist':指定绘制直方图
# bins=30:将数据范围划分为30个等宽的区间。这个值越大,柱子越细,对数据分布刻画越细致;值越小,柱子越宽,分布更平滑
# alpha=0.5:设置透明度为0.5半透明
# stacked=True:启用堆叠模式。对于每个区间,不同列(A和B)的频数会堆叠在一起,形成一个总柱子,不同颜色代表不同列。如果不堆叠(默认),两列会并排显示(并排直方图)
df[['A', 'B']].plot(kind='hist', bins=30, alpha=0.5, stacked=True)
plt.title('Pandas 直方图')
plt.show()
2
3
4
5
6
7
8
箱线图
# 箱线图(也称盒须图)通过五个统计量概括数据的分布:
# 最小值(下须端点):通常指数据中非异常值的最小值(Q1-1.5×IQR范围内)。
# 第一四分位数(Q1):数据的25%分位数,即箱子的下边界。
# 中位数(Q2):数据的50%分位数,箱子内部的横线。
# 第三四分位数(Q3):数据的75%分位数,箱子的上边界。
# 最大值(上须端点):非异常值的最大值(Q3+1.5×IQR范围内)。
# 异常值(离群点):用散点(通常是圆圈)表示落在须线范围之外的数据点。
df.plot(kind='box')
plt.title('Pandas 箱线图')
plt.show()
2
3
4
5
6
7
8
9
10
散点图
df.plot(kind='scatter', x='A', y='B', c='C', colormap='viridis')
plt.title('Pandas 散点图')
plt.show()
2
3
面积图
df.plot(kind='area', stacked=False, alpha=0.5)
plt.title('Pandas 面积图')
plt.show()
2
3
# 4.3 Seaborn 统计绘图
Seaborn 是基于 Matplotlib 的高级可视化库,它提供了更美观的默认样式和更简洁的接口,特别适合统计图表的绘制。Seaborn 通常与 Pandas 数据框结合使用。
# 4.3.1 Seaborn 风格与主题
Seaborn 自带多种主题和颜色调色板,可以轻松美化图表。
import seaborn as sns
# 设置主题
sns.set_theme(style="whitegrid") # 可选:darkgrid, whitegrid, dark, white, ticks
# 设置调色板
sns.set_palette("husl")
# 加载内置数据集
tips = sns.load_dataset("tips")
print(tips.head())
2
3
4
5
6
7
8
9
10
11
# 4.3.2 关系型图表
用于展示两个或多个变量之间的关系。
- 散点图:
scatterplot() - 折线图:
lineplot() - 关系类图:
relplot()(可绘制分面图)
import matplotlib.pyplot as plt
import seaborn as sns
# 设置字体,解决中文乱码
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False
# 加载数据
tips = sns.load_dataset("tips")
# 绘制散点图
sns.scatterplot(data=tips, x='total_bill', y='tip', hue='time', style='sex', size='size')
plt.title('小费数据散点图')
plt.show()
# 折线图
sns.lineplot(data=tips, x='size', y='tip', errorbar=('ci', 95))
plt.show()
# 分面散点图
sns.relplot(data=tips, x='total_bill', y='tip', col='time', hue='sex')
plt.show()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4.3.3 分类型图表
用于展示分类变量与数值变量的关系。
- 条形图:
barplot()(显示均值及误差条) - 箱线图:
boxplot() - 小提琴图:
violinplot()(结合箱线图与核密度估计) - 计数图:
countplot()(统计频次)
# 条形图
sns.barplot(data=tips, x='day', y='total_bill', hue='sex')
plt.title('每日消费额条形图')
plt.show()
# 箱线图
sns.boxplot(data=tips, x='day', y='total_bill', hue='smoker')
plt.show()
# 小提琴图
sns.violinplot(data=tips, x='day', y='total_bill', split=True)
plt.show()
# 计数图
sns.countplot(data=tips, x='day', hue='time')
plt.show()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4.3.4 分布型图表
用于展示单个变量或多个变量的分布。
- 直方图:
histplot()(可叠加核密度估计) - 核密度估计图:
kdeplot() - 联合分布图:
jointplot()(同时展示两个变量的分布和散点) - 配对图:
pairplot()(展示所有数值变量的两两关系)
# 直方图+核密度
sns.histplot(data=tips, x='total_bill', kde=True, bins=30)
plt.show()
# 核密度图(可分组)
sns.kdeplot(data=tips, x='total_bill', hue='time', fill=True)
plt.show()
# 联合分布图
sns.jointplot(data=tips, x='total_bill', y='tip', kind='scatter') # kind可选 'kde', 'hex', 'reg' 等
plt.show()
# 配对图(只选数值列)
sns.pairplot(tips[['total_bill', 'tip', 'size']])
plt.show()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4.3.5 回归图
展示变量间的线性关系。
regplot():绘制散点图并拟合回归线。lmplot():结合regplot和分面功能。
# 简单回归
sns.regplot(data=tips, x='total_bill', y='tip', ci=95)
plt.show()
# 分面回归
sns.lmplot(data=tips, x='total_bill', y='tip', col='day', hue='sex')
plt.show()
2
3
4
5
6
7
# 4.4 Pyecharts 交互式可视化
Pyecharts 是一个用于生成 ECharts 图表的 Python 库。ECharts 是百度开源的一个强大的数据可视化库,支持丰富的交互效果。Pyecharts 使得在 Python 中轻松创建交互式图表变得可能,尤其适合 Web 应用和 Jupyter Notebook。
# 4.4.1 Pyecharts 简介
Pyecharts 分为 v1 和 v2 版本,我们使用最新的 v2(实际上当前最新为 v2.x,但注意版本兼容性)。在 Jupyter Notebook 中,Pyecharts 可以直接渲染,无需额外配置(可能需要安装 jupyter-echarts 扩展,但一般内置支持)。如果图表不显示,可以尝试:
from pyecharts.globals import CurrentConfig, NotebookType
CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_LAB
2
# 4.4.2 基本用法
Pyecharts 的绘图流程:创建一个图表对象(如 Bar、Line),添加数据,设置全局配置项,然后在 Jupyter 中直接输出(调用 .render_notebook())或保存为 HTML 文件(.render())。
from pyecharts.charts import Bar
from pyecharts import options as opts
# 创建柱状图
bar = (
Bar()
.add_xaxis(['苹果', '香蕉', '橘子', '葡萄'])
.add_yaxis('销量', [23, 45, 56, 78])
.set_global_opts(title_opts=opts.TitleOpts(title='水果销量柱状图'))
)
# 在 Jupyter Notebook 中显示
bar.render_notebook()
2
3
4
5
6
7
8
9
10
11
12
13
# 4.4.3 常用图表
柱状图
from pyecharts.charts import Bar
from pyecharts import options as opts
bar = (
Bar()
.add_xaxis(['A', 'B', 'C', 'D'])
.add_yaxis('系列1', [10, 20, 30, 40])
.add_yaxis('系列2', [15, 25, 35, 45])
.set_global_opts(
title_opts=opts.TitleOpts(title='多系列柱状图'),
toolbox_opts=opts.ToolboxOpts(is_show=True), # 显示工具箱(保存、缩放等)
legend_opts=opts.LegendOpts(pos_left='center')
)
.set_series_opts(
label_opts=opts.LabelOpts(is_show=True) # 显示数据标签
)
)
bar.render_notebook()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
折线图
from pyecharts.charts import Line
import numpy as np
x = np.linspace(0, 2*np.pi, 20).tolist()
y = np.sin(x).tolist()
line = (
Line()
.add_xaxis(x)
.add_yaxis('sin(x)', y, is_smooth=True)
.set_global_opts(title_opts=opts.TitleOpts(title='正弦曲线'))
)
line.render_notebook()
2
3
4
5
6
7
8
9
10
11
12
13
散点图
from pyecharts.charts import Scatter
import random
data = [[random.randint(0, 100), random.randint(0, 100)] for _ in range(50)]
scatter = (
Scatter()
.add_xaxis([d[0] for d in data])
.add_yaxis('随机点', [d[1] for d in data])
.set_global_opts(
title_opts=opts.TitleOpts(title='散点图'),
xaxis_opts=opts.AxisOpts(name='X'),
yaxis_opts=opts.AxisOpts(name='Y'),
visualmap_opts=opts.VisualMapOpts() # 添加视觉映射组件
)
)
scatter.render_notebook()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
饼图
from pyecharts.charts import Pie
data_pair = [('苹果', 25), ('香蕉', 35), ('橘子', 20), ('葡萄', 20)]
pie = (
Pie()
.add('', data_pair)
.set_global_opts(title_opts=opts.TitleOpts(title='水果占比'))
.set_series_opts(label_opts=opts.LabelOpts(formatter='{b}: {d}%')) # 显示百分比
)
pie.render_notebook()
2
3
4
5
6
7
8
9
10
11
地图
Pyecharts 支持中国地图、世界地图等,需要安装扩展包:
pip install echarts-china-provinces-pypkg echarts-china-cities-pypkg echarts-china-counties-pypkg
from pyecharts.charts import Map
from pyecharts import options as opts
# 示例:中国各省份数据
data = [('广东省', 100), ('江苏省', 80), ('浙江省', 70), ('北京市', 60), ('上海市', 90)]
map_chart = (
Map()
.add('', data, 'china')
.set_global_opts(
title_opts=opts.TitleOpts(title='中国地图示例'),
visualmap_opts=opts.VisualMapOpts(max_=100, is_piecewise=True)
)
)
map_chart.render_notebook()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4.4.4 多图表组合
使用 Grid、Tab 等组件可以将多个图表组合在一起。
from pyecharts.charts import Grid, Bar, Line
bar = (
Bar()
.add_xaxis(['A', 'B', 'C'])
.add_yaxis('销量', [23, 45, 56])
)
line = (
Line()
.add_xaxis(['A', 'B', 'C'])
.add_yaxis('增长率', [0.2, 0.3, 0.1])
)
grid = (
Grid()
.add(bar, grid_opts=opts.GridOpts(pos_bottom='60%'))
.add(line, grid_opts=opts.GridOpts(pos_top='60%'))
)
grid.render_notebook()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4.4.5 在 Jupyter Notebook 中的显示注意事项
- 确保 notebook 是最新版本,并安装了
ipywidgets。 - 如果图表不显示,可以尝试调用
chart.render_notebook()后检查是否有 JavaScript 错误。 - 也可以使用
chart.render('output.html')生成 HTML 文件,在浏览器中打开。
# 小结
数据可视化是数据分析不可或缺的环节。本部分介绍了从基础静态绘图(Matplotlib、Pandas内置绘图、Seaborn)到交互式绘图(Pyecharts)的完整体系。Matplotlib 提供了底层控制,Pandas 内置绘图适合快速探索,Seaborn 让统计图表更加美观易用,而 Pyecharts 则为生成交互式图表提供了便捷途径。掌握这些工具,您可以根据需求选择合适的可视化方式。
# 5. 综合应用案例——Kaggle电商客户行为分析
在学习了NumPy、Pandas以及多种可视化工具后,我们通过一个真实的Kaggle数据集来串联所有知识点。本案例将使用E-Commerce Customer Behavior & Sales Dataset - TR(土耳其电商客户行为数据集),该数据集包含5000条真实交易记录,涵盖了客户 demographics、购买行为、产品偏好和 engagement 指标等多个维度。
# 5.1 案例背景与目标
数据集来源:Kaggle平台上的"E-Commerce Customer Behavior & Sales Analysis -TR"数据集,我们使用的是ecommerce_customer_behavior_dataset.csv这个文件,还有一个ecommerce_customer_behavior_dataset_v2.csv的版本,数据量有1.7W+。
数据集概览:
- 记录数:5000条交易记录
- 时间范围:2023年1月至2024年3月
- 字段数:18列
- 数据质量:无缺失值,格式一致
字段说明:
| 字段名 | 类型 | 说明 |
|---|---|---|
| Order_ID | 字符串 | 唯一订单标识 |
| Customer_ID | 字符串 | 唯一客户标识 |
| Date | 日期 | 交易日期 |
| Age | 整数 | 客户年龄(18-75岁) |
| Gender | 字符串 | 性别(Male/Female/Other) |
| City | 字符串 | 客户所在城市 |
| Product_Category | 字符串 | 产品类别(8类) |
| Unit_Price | 浮点数 | 单价 |
| Quantity | 整数 | 购买数量(1-5) |
| Discount_Amount | 浮点数 | 折扣金额 |
| Total_Amount | 浮点数 | 最终支付金额 |
| Payment_Method | 字符串 | 支付方式 |
| Device_Type | 字符串 | 设备类型(Mobile/Desktop/Tablet) |
| Session_Duration_Minutes | 整数 | 会话时长(1-120分钟) |
| Pages_Viewed | 整数 | 浏览页面数(1-50) |
| Is_Returning_Customer | 布尔值 | 是否回头客 |
| Delivery_Time_Days | 整数 | 配送天数(1-30天) |
| Customer_Rating | 整数 | 客户评分(1-5星) |
分析目标:
- 客户画像分析:了解不同年龄、性别、地区的客户消费特征
- 销售趋势分析:识别季节性模式和销售高峰期
- 产品类别表现:比较各类别的销售额、评分和退货情况
- 设备行为分析:不同设备类型的购买行为差异
- 客户忠诚度分析:回头客与新客户的行为对比
- 配送与满意度关系:分析配送时间对客户评分的影响
# 5.2 数据获取与加载
# 5.2.1 从Kaggle下载数据集
方式一:直接从Kaggle网站下载
- 访问 https://www.kaggle.com/datasets/umuttuygurr/e-commerce-customer-behavior-and-sales-analysis-tr
- 点击"Download"按钮下载CSV文件
方式二:使用Kaggle API(推荐,便于复现)
# 安装Kaggle API
pip install kaggle
# 配置API凭证(需要先在Kaggle账号中创建API token)
# 将下载的kaggle.json放在/usr/software/jupyter/目录下
# 下载数据集
kaggle datasets download -d umuttuygurr/e-commerce-customer-behavior-and-sales-analysis-tr -p /usr/software/jupyter
# 解压
unzip /usr/software/jupyter/e-commerce-customer-behavior-and-sales-analysis-tr.zip -d /usr/software/jupyter
2
3
4
5
6
7
8
9
10
# 5.2.2 加载数据集
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
# 设置中文显示(如果需要在图中显示中文)
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False
# 加载数据
df = pd.read_csv('ecommerce_customer_behavior_dataset.csv') # 文件名根据实际下载情况调整
# 查看数据基本信息
print("数据集形状:", df.shape)
print("\n数据前5行:")
df.head()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 5.2.3 初步探索
# 数据概览
df.info()
# 描述性统计
df.describe(include='all')
# 检查缺失值
print("缺失值统计:")
print(df.isnull().sum()) # 根据数据集说明,应该无缺失值
# 数据类型确认
print("\n数据类型:")
print(df.dtypes)
2
3
4
5
6
7
8
9
10
11
12
13
# 5.3 数据清洗与预处理
根据数据集说明,该数据集已经过清洗,无缺失值。但为了教学完整性,我们仍然进行一些必要的预处理。
# 5.3.1 数据类型转换
# 将Date列转换为datetime类型
df['Date'] = pd.to_datetime(df['Date'])
# 将Is_Returning_Customer转换为整数(0/1)
df['Is_Returning_Customer'] = df['Is_Returning_Customer'].astype(int)
# 确认转换后的类型
print(df.dtypes)
2
3
4
5
6
7
8
# 5.3.2 添加衍生特征
# 提取时间相关特征
df['Year'] = df['Date'].dt.year
df['Month'] = df['Date'].dt.month
df['Quarter'] = df['Date'].dt.quarter
df['DayOfWeek'] = df['Date'].dt.dayofweek # 0=周一, 6=周日
df['IsWeekend'] = (df['DayOfWeek'] >= 5).astype(int)
# 计算平均单价(验证Unit_Price * Quantity - Discount = Total_Amount)
df['Calculated_Total'] = df['Unit_Price'] * df['Quantity'] - df['Discount_Amount']
df['Amount_Check'] = np.abs(df['Calculated_Total'] - df['Total_Amount']) < 0.01
print("金额计算验证通过率:", df['Amount_Check'].mean() * 100, "%")
# 计算折扣率
df['Discount_Rate'] = (df['Discount_Amount'] / (df['Unit_Price'] * df['Quantity'])) * 100
# 无折扣的为0
df['Discount_Rate'] = df['Discount_Rate'].fillna(0)
# 创建年龄分组
bins = [0, 25, 35, 45, 55, 100]
labels = ['18-25', '26-35', '36-45', '46-55', '56+']
df['Age_Group'] = pd.cut(df['Age'], bins=bins, labels=labels, right=False)
# 创建评分分组
df['Rating_Group'] = pd.cut(df['Customer_Rating'],
bins=[0, 2, 3, 4, 5],
labels=['低分(1-2)', '中低分(3)', '中高分(4)', '高分(5)'])
print("衍生特征添加完成")
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
# 5.3.3 异常值检测
# 使用箱线图检测异常值
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
numeric_cols = ['Age', 'Unit_Price', 'Quantity', 'Total_Amount',
'Session_Duration_Minutes', 'Delivery_Time_Days']
for i, col in enumerate(numeric_cols):
row, col_idx = i // 3, i % 3
axes[row, col_idx].boxplot(df[col])
axes[row, col_idx].set_title(f'{col} 箱线图')
axes[row, col_idx].set_ylabel('值')
plt.tight_layout()
plt.show()
# 使用IQR方法识别异常值
# IQR规则:通常将低于Q1-1.5×IQR 或高于Q3+1.5×IQR的值视为异常值。这是统计学中常用的识别离群点的标准
def detect_outliers_iqr(data, column):
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
return outliers, lower_bound, upper_bound
for col in numeric_cols:
outliers, lb, ub = detect_outliers_iqr(df, col)
print(f"{col}: 异常值数量 {len(outliers)} ({len(outliers)/len(df)*100:.2f}%)")
# 对于异常值,我们保留但标记
df['Is_High_Value'] = (df['Total_Amount'] > df['Total_Amount'].quantile(0.95)).astype(int)
df['Is_Long_Delivery'] = (df['Delivery_Time_Days'] > df['Delivery_Time_Days'].quantile(0.95)).astype(int)
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
# 5.4 探索性数据分析(EDA)
# 5.4.1 整体业务概况
# 关键指标汇总
total_sales = df['Total_Amount'].sum()
total_orders = df['Order_ID'].nunique()
total_customers = df['Customer_ID'].nunique()
avg_order_value = total_sales / total_orders
avg_rating = df['Customer_Rating'].mean() # 客户评分
returning_rate = df['Is_Returning_Customer'].mean() * 100 #是否回头客
print("="*50)
print("业务概况")
print("="*50)
print(f"总销售额: {total_sales:,.2f} TRY")
print(f"总订单数: {total_orders}")
print(f"总客户数: {total_customers}")
print(f"平均客单价: {avg_order_value:.2f} TRY")
print(f"平均客户评分: {avg_rating:.2f}/5.0")
print(f"回头客比例: {returning_rate:.1f}%")
print(f"移动端交易占比: {(df['Device_Type']=='Mobile').mean()*100:.1f}%") # 数据集显示约55%
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 5.4.2 销售趋势分析
# 月度销售额趋势
# df['Date'].dt.to_period('M'):将Date列转换为月度周期(例如 2023-01),用于按月份分组
# .groupby(...)['Total_Amount'].agg(['sum', 'count', 'mean']):对每个月的Total_Amount列同时计算总和(总销售额)、订单数量(count)和平均值(平均客单价)
monthly_sales = df.groupby(df['Date'].dt.to_period('M'))['Total_Amount'].agg(['sum', 'count', 'mean'])
# 将聚合后的列名改为中文,便于理解
monthly_sales.columns = ['总销售额', '订单数', '平均客单价']
# 索引转换:将月度周期索引(Period类型)转为字符串,便于后续绘图时作为横坐标标签
monthly_sales.index = monthly_sales.index.astype(str)
# 绘制趋势图
fig, axes = plt.subplots(3, 1, figsize=(14, 10))
# 销售额趋势
axes[0].plot(monthly_sales.index, monthly_sales['总销售额'], marker='o', color='blue')
axes[0].set_title('月度销售额趋势 (2023-2024)')
axes[0].set_ylabel('销售额 (TRY)')
axes[0].tick_params(axis='x', rotation=45)
# 订单量趋势
axes[1].plot(monthly_sales.index, monthly_sales['订单数'], marker='s', color='green')
axes[1].set_title('月度订单量趋势')
axes[1].set_ylabel('订单数')
axes[1].tick_params(axis='x', rotation=45)
# 客单价趋势
axes[2].plot(monthly_sales.index, monthly_sales['平均客单价'], marker='^', color='red')
axes[2].set_title('月度平均客单价趋势')
axes[2].set_ylabel('平均客单价 (TRY)')
axes[2].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
# 识别销售高峰期
# nlargest(3, '总销售额'):从monthly_sales中找出总销售额最高的3个月,并保留这两列(总销售额和订单数)
peak_months = monthly_sales.nlargest(3, '总销售额')[['总销售额', '订单数']]
print("\n销售高峰期TOP3:")
print(peak_months)
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
# 5.4.3 客户画像分析
# 性别分布与消费特征
# 按 Gender性别分组
# Customer_ID用'nunique'(去重计数):统计不同性别下的独立客户数量(而非订单数)。
# Total_Amount同时计算总和sum和均值mean,得到该性别的总销售额和平均客单价。
# Order_ID用'count':统计该性别下的总订单数。
# Customer_Rating用'mean':计算该性别客户的平均评分。
gender_analysis = df.groupby('Gender').agg({
'Customer_ID': 'nunique',
'Total_Amount': ['sum', 'mean'],
'Order_ID': 'count',
'Customer_Rating': 'mean'
}).round(2)
# 列重命名:将多级索引简化成直观的中文列名,便于阅读
gender_analysis.columns = ['客户数', '总销售额', '平均客单价', '订单数', '平均评分']
print("性别分析:")
print(gender_analysis)
# 年龄组分析
# 分组键:Age_Group 是之前通过分箱创建的年龄段列,如 18-25、26-35等。
# observed=True:当分组列为Categorical类型时,此参数确保只显示数据中实际出现的组,而非所有可能的类别(避免出现空组)。如果不设置,可能会显示所有理论分组。
# 聚合指标:与性别分析相同,包括客户数、总销售额、平均客单价、订单数、平均评分。
age_analysis = df.groupby('Age_Group', observed=True).agg({
'Customer_ID': 'nunique',
'Total_Amount': ['sum', 'mean'],
'Order_ID': 'count',
'Customer_Rating': 'mean'
}).round(2)
age_analysis.columns = ['客户数', '总销售额', '平均客单价', '订单数', '平均评分']
print("\n年龄组分析:")
print(age_analysis)
# 城市分析
# 分组:按City城市分组。
# 聚合指标:总销售额、独立客户数、订单数、平均评分。
# 排序:按总销售额降序排列,便于查看销售表现最好的城市。
city_analysis = df.groupby('City').agg({
'Total_Amount': 'sum',
'Customer_ID': 'nunique',
'Order_ID': 'count',
'Customer_Rating': 'mean'
}).round(2)
city_analysis.columns = ['总销售额', '客户数', '订单数', '平均评分']
city_analysis = city_analysis.sort_values('总销售额', ascending=False)
print("\n城市销售排行TOP5:")
print(city_analysis.head())
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
# 5.4.4 产品类别分析
# 各类别销售表现
category_analysis = df.groupby('Product_Category').agg({
'Total_Amount': ['sum', 'mean', 'count'],
'Quantity': 'sum',
'Customer_Rating': 'mean',
'Discount_Amount': 'sum',
'Is_Returning_Customer': 'mean'
}).round(2)
category_analysis.columns = ['总销售额', '平均客单价', '订单数', '总销量', '平均评分', '折扣总额', '回头客比例']
category_analysis = category_analysis.sort_values('总销售额', ascending=False)
print("产品类别分析:")
print(category_analysis)
# 可视化各类别表现
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
# 销售额占比
axes[0,0].pie(category_analysis['总销售额'], labels=category_analysis.index, autopct='%1.1f%%')
axes[0,0].set_title('各类别销售额占比')
# 平均评分
sns.barplot(x=category_analysis.index, y=category_analysis['平均评分'], ax=axes[0,1])
axes[0,1].set_title('各类别平均评分')
axes[0,1].tick_params(axis='x', rotation=45)
# 订单量
sns.barplot(x=category_analysis.index, y=category_analysis['订单数'], ax=axes[1,0])
axes[1,0].set_title('各类别订单量')
axes[1,0].tick_params(axis='x', rotation=45)
# 平均客单价
sns.barplot(x=category_analysis.index, y=category_analysis['平均客单价'], ax=axes[1,1])
axes[1,1].set_title('各类别平均客单价')
axes[1,1].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
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
# 5.4.5 设备行为分析
# 不同设备的购买行为
device_analysis = df.groupby('Device_Type').agg({
'Order_ID': 'count',
'Total_Amount': ['sum', 'mean'],
'Session_Duration_Minutes': 'mean',
'Pages_Viewed': 'mean',
'Customer_Rating': 'mean',
'Is_Returning_Customer': 'mean'
}).round(2)
device_analysis.columns = ['订单数', '总销售额', '平均客单价', '平均会话时长', '平均浏览页数', '平均评分', '回头客比例']
print("设备类型分析:")
print(device_analysis)
# 设备类型与购买金额关系
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
sns.boxplot(data=df, x='Device_Type', y='Total_Amount', ax=axes[0])
axes[0].set_title('不同设备的客单价分布')
sns.boxplot(data=df, x='Device_Type', y='Session_Duration_Minutes', ax=axes[1])
axes[1].set_title('不同设备的会话时长分布')
sns.boxplot(data=df, x='Device_Type', y='Pages_Viewed', ax=axes[2])
axes[2].set_title('不同设备的浏览页数分布')
plt.tight_layout()
plt.show()
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
# 5.4.6 客户忠诚度分析
# 回头客 vs 新客户
loyalty_analysis = df.groupby('Is_Returning_Customer').agg({
'Order_ID': 'count',
'Total_Amount': ['sum', 'mean'],
'Customer_Rating': 'mean',
'Session_Duration_Minutes': 'mean',
'Pages_Viewed': 'mean',
'Delivery_Time_Days': 'mean'
}).round(2)
loyalty_analysis.columns = ['订单数', '总销售额', '平均客单价', '平均评分', '平均会话时长', '平均浏览页数', '平均配送时间']
loyalty_analysis.index = ['新客户', '回头客']
print("客户忠诚度分析:")
print(loyalty_analysis)
# 客户购买频次分布
customer_frequency = df.groupby('Customer_ID')['Order_ID'].count().reset_index()
customer_frequency.columns = ['Customer_ID', 'Purchase_Count']
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 购买频次分布
sns.histplot(customer_frequency['Purchase_Count'], bins=20, kde=True, ax=axes[0])
axes[0].set_title('客户购买频次分布')
axes[0].set_xlabel('购买次数')
axes[0].set_ylabel('客户数')
# 帕累托分析(累计销售额贡献)
customer_value = df.groupby('Customer_ID')['Total_Amount'].sum().sort_values(ascending=False).reset_index()
customer_value['Cumulative_Pct'] = customer_value['Total_Amount'].cumsum() / customer_value['Total_Amount'].sum() * 100
axes[1].plot(range(1, len(customer_value)+1), customer_value['Cumulative_Pct'])
axes[1].axhline(y=80, color='r', linestyle='--', label='80% 销售额')
axes[1].axvline(x=len(customer_value)*0.2, color='g', linestyle='--', label='20% 客户')
axes[1].set_title('帕累托分析:客户累计销售额贡献')
axes[1].set_xlabel('客户数(按价值排序)')
axes[1].set_ylabel('累计销售额占比 (%)')
axes[1].legend()
plt.tight_layout()
plt.show()
# 计算前20%客户贡献的销售额比例
top_20_pct = int(len(customer_value) * 0.2)
top_20_contribution = customer_value.iloc[:top_20_pct]['Total_Amount'].sum() / customer_value['Total_Amount'].sum() * 100
print(f"前20%的客户贡献了 {top_20_contribution:.1f}% 的销售额")
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
# 5.4.7 配送时间与客户满意度
# 配送时间分析
delivery_analysis = df.groupby('Delivery_Time_Days').agg({
'Customer_Rating': 'mean',
'Order_ID': 'count'
}).reset_index()
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 配送时间分布
sns.histplot(df['Delivery_Time_Days'], bins=30, kde=True, ax=axes[0])
axes[0].set_title('配送时间分布')
axes[0].set_xlabel('配送天数')
axes[0].set_ylabel('订单数')
# 配送时间与评分关系
sns.scatterplot(data=delivery_analysis, x='Delivery_Time_Days', y='Customer_Rating',
size='Order_ID', sizes=(20, 200), ax=axes[1])
axes[1].set_title('配送时间 vs 客户评分')
axes[1].set_xlabel('配送天数')
axes[1].set_ylabel('平均客户评分')
plt.tight_layout()
plt.show()
# 计算相关性
correlation = df['Delivery_Time_Days'].corr(df['Customer_Rating'])
print(f"配送时间与客户评分的相关系数: {correlation:.3f}")
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
# 5.5 高级分析与可视化
# 5.5.1 相关性热力图
# 选择数值列进行相关性分析
numeric_df = df[['Age', 'Unit_Price', 'Quantity', 'Total_Amount', 'Discount_Amount',
'Session_Duration_Minutes', 'Pages_Viewed', 'Is_Returning_Customer',
'Delivery_Time_Days', 'Customer_Rating']]
correlation_matrix = numeric_df.corr()
plt.figure(figsize=(12, 10))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0,
square=True, linewidths=1, cbar_kws={"shrink": 0.8})
plt.title('数值特征相关性热力图')
plt.tight_layout()
plt.show()
2
3
4
5
6
7
8
9
10
11
12
13
# 5.5.2 使用Pyecharts创建交互式仪表板
from pyecharts.charts import Bar, Line, Pie, Scatter, Grid, Page
from pyecharts import options as opts
from pyecharts.globals import ThemeType
# 准备数据
monthly_data = monthly_sales.reset_index()
# 动态获取索引列名
index_col_name = monthly_data.columns[0]
months = monthly_data[index_col_name].tolist()
sales_values = monthly_data['总销售额'].tolist()
order_values = monthly_data['订单数'].tolist()
# 1. 月度销售额折线图
line_chart = (
Line()
.add_xaxis(months)
.add_yaxis("销售额", sales_values, is_smooth=True, color="#5470C6")
.set_global_opts(
title_opts=opts.TitleOpts(title="月度销售额趋势"),
tooltip_opts=opts.TooltipOpts(trigger="axis"),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=45)),
yaxis_opts=opts.AxisOpts(name="销售额 (TRY)"),
legend_opts=opts.LegendOpts(pos_left="center")
)
)
# 2. 类别销售额柱状图
category_data = category_analysis.reset_index()
categories = category_data['Product_Category'].tolist()
category_sales = category_data['总销售额'].tolist()
bar_chart = (
Bar()
.add_xaxis(categories)
.add_yaxis("销售额", category_sales, color="#91CC75")
.set_global_opts(
title_opts=opts.TitleOpts(title="各类别销售额"),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=45)),
yaxis_opts=opts.AxisOpts(name="销售额")
)
.set_series_opts(label_opts=opts.LabelOpts(is_show=True))
)
# 3. 支付方式饼图
payment_counts = df['Payment_Method'].value_counts()
payment_data = [(k, v) for k, v in payment_counts.items()]
pie_chart = (
Pie()
.add("", payment_data, radius=["40%", "70%"])
.set_global_opts(
title_opts=opts.TitleOpts(title="支付方式分布"),
legend_opts=opts.LegendOpts(orient="vertical", pos_top="15%", pos_left="2%")
)
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {d}%"))
)
# 4. 设备类型与客单价散点图
device_sample = df.sample(min(500, len(df)))
device_scatter = (
Scatter()
.add_xaxis(device_sample['Device_Type'].tolist())
.add_yaxis("客单价", device_sample['Total_Amount'].tolist(),
label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(
title_opts=opts.TitleOpts(title="不同设备客单价分布"),
xaxis_opts=opts.AxisOpts(name="设备类型"),
yaxis_opts=opts.AxisOpts(name="客单价")
)
)
# 在Jupyter中显示
line_chart.render_notebook()
bar_chart.render_notebook()
pie_chart.render_notebook()
device_scatter.render_notebook()
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
# 5.5.3 客户分群(K-Means聚类)
根据客户消费行为进行分群,这是电商分析中常用的技术。
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
# 准备客户级别特征
customer_features = df.groupby('Customer_ID').agg({
'Total_Amount': ['sum', 'mean'],
'Order_ID': 'count',
'Customer_Rating': 'mean',
'Session_Duration_Minutes': 'mean',
'Pages_Viewed': 'mean',
'Delivery_Time_Days': 'mean',
'Discount_Rate': 'mean'
}).round(2)
# 扁平化列名
customer_features.columns = ['总消费额', '平均客单价', '购买次数',
'平均评分', '平均会话时长', '平均浏览页数',
'平均配送时间', '平均折扣率']
# 重置索引使Customer_ID成为列
customer_features = customer_features.reset_index()
# 标准化特征
scaler = StandardScaler()
features_for_clustering = ['总消费额', '购买次数', '平均客单价', '平均评分']
scaled_features = scaler.fit_transform(customer_features[features_for_clustering])
# 使用肘部法则确定最佳聚类数
inertias = []
K_range = range(2, 9)
for k in K_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(scaled_features)
inertias.append(kmeans.inertia_)
# 绘制肘部图
plt.figure(figsize=(10, 6))
plt.plot(K_range, inertias, 'bo-')
plt.xlabel('聚类数 K')
plt.ylabel('惯性 (Inertia)')
plt.title('肘部法则确定最佳K值')
plt.grid(True)
plt.show()
# 选择K=4进行聚类
kmeans = KMeans(n_clusters=4, random_state=42, n_init=10)
customer_features['Cluster'] = kmeans.fit_predict(scaled_features)
# 分析各簇特征
cluster_analysis = customer_features.groupby('Cluster')[features_for_clustering].mean()
print("各客户群特征:")
print(cluster_analysis)
# 使用PCA降维可视化
pca = PCA(n_components=2)
pca_result = pca.fit_transform(scaled_features)
plt.figure(figsize=(12, 8))
scatter = plt.scatter(pca_result[:, 0], pca_result[:, 1],
c=customer_features['Cluster'], cmap='viridis', alpha=0.6)
plt.colorbar(scatter, label='聚类')
plt.xlabel('第一主成分')
plt.ylabel('第二主成分')
plt.title('客户聚类可视化 (PCA降维)')
plt.show()
# 为各簇命名
cluster_names = {
0: '高价值忠诚客户',
1: '普通大众客户',
2: '潜力新客户',
3: '低价值客户'
}
customer_features['Cluster_Name'] = customer_features['Cluster'].map(cluster_names)
# 各簇占比
cluster_distribution = customer_features['Cluster_Name'].value_counts()
print("\n各客户群占比:")
print(cluster_distribution)
print(cluster_distribution / len(customer_features) * 100)
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
# 5.5.4 评分预测建模(基础机器学习)
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
# 准备特征和目标变量
feature_cols = ['Age', 'Unit_Price', 'Quantity', 'Discount_Rate',
'Session_Duration_Minutes', 'Pages_Viewed', 'Is_Returning_Customer',
'Delivery_Time_Days']
target_col = 'Customer_Rating'
# 对分类变量进行编码
df_encoded = pd.get_dummies(df, columns=['Gender', 'Product_Category',
'Payment_Method', 'Device_Type'])
# 选择特征列
X = df_encoded[feature_cols + [col for col in df_encoded.columns if col.startswith(('Gender_', 'Product_Category_', 'Payment_Method_', 'Device_Type_'))]]
y = df_encoded[target_col]
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 训练随机森林模型
rf_model = RandomForestRegressor(n_estimators=100, max_depth=10, random_state=42)
rf_model.fit(X_train, y_train)
# 预测
y_pred = rf_model.predict(X_test)
# 评估
mse = mean_squared_error(y_test, y_pred)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print("模型评估结果:")
print(f"均方误差 (MSE): {mse:.4f}")
print(f"平均绝对误差 (MAE): {mae:.4f}")
print(f"R² 分数: {r2:.4f}")
# 特征重要性
feature_importance = pd.DataFrame({
'feature': X.columns,
'importance': rf_model.feature_importances_
}).sort_values('importance', ascending=False)
print("\n特征重要性TOP10:")
print(feature_importance.head(10))
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
# 5.6 结论与商业建议
# 5.6.1 关键发现总结
# 汇总关键指标
insights = {
"客户概况": {
"回头客比例": f"{returning_rate:.1f}%",
"移动端占比": f"{(df['Device_Type']=='Mobile').mean()*100:.1f}%",
"平均客单价": f"{avg_order_value:.2f} TRY",
"平均评分": f"{avg_rating:.2f}/5.0"
},
"销售表现": {
"总销售额": f"{total_sales:,.2f} TRY",
"总订单数": f"{total_orders}",
"高峰期": f"{peak_months.index[0]} (销售额{peak_months.iloc[0]['总销售额']:,.0f} TRY)"
},
"产品表现": {
"最畅销类别": category_analysis.index[0],
"评分最高类别": category_analysis['平均评分'].idxmax(),
"客单价最高类别": category_analysis['平均客单价'].idxmax()
}
}
print("\n" + "="*60)
print("关键发现总结")
print("="*60)
for category, metrics in insights.items():
print(f"\n{category}:")
for key, value in metrics.items():
print(f" - {key}: {value}")
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
# 5.6.2 商业建议
基于以上分析,提出以下商业建议:
recommendations = [
{
"领域": "客户细分与精准营销",
"发现": f"前20%的客户贡献了{top_20_contribution:.1f}%的销售额,符合帕累托法则",
"建议": "针对高价值客户(聚类中的0簇)建立VIP计划,提供专属优惠和快速配送;针对潜力客户(2簇)推送个性化推荐,提升转化率"
},
{
"领域": "产品策略优化",
"发现": f"{category_analysis.index[0]}类别销售额最高,但评分最高的类别是{category_analysis['平均评分'].idxmax()}",
"建议": "保持{category_analysis.index[0]}类别的库存充足,同时分析{category_analysis['平均评分'].idxmax()}类别的成功因素,应用到其他类别"
},
{
"领域": "移动端体验优化",
"发现": f"移动端交易占比{(df['Device_Type']=='Mobile').mean()*100:.1f}%,但平均客单价低于桌面端",
"建议": "优化移动端购物流程,简化支付步骤,考虑推出移动端专属优惠"
},
{
"领域": "配送服务改进",
"发现": f"配送时间与客户评分相关系数为{correlation:.3f},呈负相关",
"建议": "对配送时间超过7天的订单主动跟踪并提供补偿;考虑在高峰期增加配送人力"
},
{
"领域": "客户忠诚度提升",
"发现": f"回头客比例{returning_rate:.1f}%,且回头客的客单价和评分均高于新客户",
"建议": "实施会员积分制度,对重复购买客户提供累计折扣;定期发送个性化推荐邮件"
}
]
print("\n" + "="*60)
print("商业建议")
print("="*60)
for i, rec in enumerate(recommendations, 1):
print(f"\n{i}. {rec['领域']}")
print(f" 📊 发现: {rec['发现']}")
print(f" 💡 建议: {rec['建议']}")
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
# 5.6.3 未来研究方向
future_work = [
"时间序列预测:使用ARIMA或Prophet模型预测未来销售趋势",
"推荐系统:基于用户购买历史构建协同过滤推荐引擎",
"客户生命周期价值(CLV)预测:预估客户长期价值,优化营销预算分配",
"A/B测试框架:设计实验验证营销策略的有效性",
"自然语言处理:如果获取到评论数据,可进行情感分析"
]
print("\n" + "="*60)
print("未来研究方向")
print("="*60)
for item in future_work:
print(f" • {item}")
2
3
4
5
6
7
8
9
10
11
12
13
# 5.7 代码完整性与可复现性
为确保分析的可复现性,建议将所有代码整合到一个Jupyter Notebook中,并包含以下设置:
# 环境配置和依赖
import sys
print("Python版本:", sys.version)
print("Pandas版本:", pd.__version__)
print("NumPy版本:", np.__version__)
print("Matplotlib版本:", plt.matplotlib.__version__)
print("Seaborn版本:", sns.__version__)
# 如果需要保存处理后的数据
df.to_csv('processed_ecommerce_data.csv', index=False)
customer_features.to_csv('customer_segments.csv', index=False)
# 保存可视化图表
plt.savefig('sales_trend.png', dpi=300, bbox_inches='tight')
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5.8 案例总结
本案例基于Kaggle平台的真实电商数据集,完整演示了数据分析的全流程:
- 数据获取:使用Kaggle API下载真实数据集
- 数据清洗:处理数据类型、添加衍生特征、检测异常值
- 探索性分析:从客户画像、产品表现、设备行为、客户忠诚度等多个维度深入分析
- 高级分析:相关性分析、客户聚类分群、基础预测建模
- 可视化呈现:综合使用Matplotlib、Seaborn和Pyecharts创建静态与交互式图表
- 商业洞察:基于数据得出 actionable 的商业建议
通过本案例,读者可以掌握处理真实数据集的能力,理解如何将数据分析技术应用于实际业务场景,并能够从数据中提炼有价值的商业洞察。