# Python数据分析全栈教程:从环境搭建到Kaggle实战

在开始数据处理与统计分析之前,我们需要搭建一个稳定、可复现的 Python 环境。本部分将详细介绍如何在 Ubuntu 24.04 LTS 上配置 Python 虚拟环境、安装 JupyterLab 并设置其为系统服务以实现开机自启动,同时安装常用的数据科学包。所有操作均基于终端命令行,并遵循系统管理员的最佳实践。

# 1.1 系统准备

# 1.1.1 更新系统包

确保系统包列表和已安装的软件包是最新的:

sudo apt update && sudo apt upgrade -y
1

# 1.1.2 安装 Python 及虚拟环境工具

Ubuntu 24.04 默认包含 Python 3.12,我们只需安装 pip 和虚拟环境管理工具:

sudo apt install python3 python3-pip python3-venv -y
1

# 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
1
2
3
4

创建 Python 虚拟环境:

python3 -m venv venv
1

激活虚拟环境(后续所有包安装均在此环境中进行):

source venv/bin/activate
1

激活后,命令行提示符前会出现(venv)标识。

# 1.3 安装 JupyterLab 及其依赖

JupyterLab 是下一代 Jupyter Notebook 的交互式开发环境,适合数据科学工作流。

# 1.3.1 安装 Node.js(JupyterLab 扩展所需)

某些 JupyterLab 扩展需要 Node.js 运行时,我们一并安装:

sudo apt install nodejs npm -y
1

# 1.3.2 安装 JupyterLab

在虚拟环境中使用 pip 安装 JupyterLab:

pip install jupyterlab
1

如需经典 Jupyter Notebook,可替换为 jupyter notebook,但本教程推荐 JupyterLab。

# 1.4 配置 JupyterLab

# 1.4.1 生成配置文件

运行以下命令生成 Jupyter 配置文件:

jupyter server --generate-config
1

配置文件默认生成在 ~/.jupyter/jupyter_server_config.py。

# 1.4.2 设置访问密码

为了安全,建议设置登录密码:

jupyter server password
1

根据提示输入并确认密码,密码的哈希值将保存在 ~/.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   # 赋予当前用户权限
1
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
1
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
1

填入以下内容:

[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
1
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
1
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
1
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
1
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
1

安装完成后,重启 JupyterLab 服务以使扩展生效:

sudo systemctl restart hep-jupyter.service
1

# 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__)
1
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.  ]
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
1
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
1
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]]
1
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]]
1
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]
1
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]]
1
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]
1
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]
1
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)
1
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]
1
2
3
4
5
6
7
8

# 2.4.2 比较运算

print(a > 2)       # [False False  True]
print(a == b)      # [False False False]
1
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]
1
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]]
1
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]]
1
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]]
1
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.]
1
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)
1
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]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 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]]
1
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)
1
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]
1
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)
1
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)
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Series 的常用属性:

print(s2.values)      # [1 2 3]
print(s2.index)       # Index(['a', 'b', 'c'], dtype='str')
print(s2.dtype)       # int64
print(s2.shape)       # (3,)
1
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
1
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())     # 查看数据信息,包括非空计数
1
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

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())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 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  # 不推荐,容易与现有方法冲突
1
2
3
4
5
6
7
8
9

# 3.4.2 行选择

使用 [] 切片(按行位置):

# 前两行
print(df[:2])

# 第1行到第3行(不含第3行)
print(df[1:3])
1
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']])
1
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])
1
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'])])
1
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()       # 线性插值
1
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'])  # 根据特定列去重
1
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')
# 或者从某列解析日期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 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
1
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)
1
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)
1
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的行
1
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())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 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)
1
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>
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 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)
1
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)
1
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
1
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
1
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 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')
1
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
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()
1
2
3

面积图

df.plot(kind='area', stacked=False, alpha=0.5)
plt.title('Pandas 面积图')
plt.show()
1
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())
1
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()
1
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()
1
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()
1
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()
1
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
1
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()
1
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()
1
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()
1
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()
1
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()
1
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
1
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()
1
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()
1
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星)

分析目标:

  1. 客户画像分析:了解不同年龄、性别、地区的客户消费特征
  2. 销售趋势分析:识别季节性模式和销售高峰期
  3. 产品类别表现:比较各类别的销售额、评分和退货情况
  4. 设备行为分析:不同设备类型的购买行为差异
  5. 客户忠诚度分析:回头客与新客户的行为对比
  6. 配送与满意度关系:分析配送时间对客户评分的影响

# 5.2 数据获取与加载

# 5.2.1 从Kaggle下载数据集

方式一:直接从Kaggle网站下载

  1. 访问 https://www.kaggle.com/datasets/umuttuygurr/e-commerce-customer-behavior-and-sales-analysis-tr
  2. 点击"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
1
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()
1
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)
1
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)
1
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("衍生特征添加完成")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 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%
1
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 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())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 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}% 的销售额")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 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}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 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()
1
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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

# 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

# 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))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 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}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 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['建议']}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 5.6.3 未来研究方向

future_work = [
    "时间序列预测:使用ARIMA或Prophet模型预测未来销售趋势",
    "推荐系统:基于用户购买历史构建协同过滤推荐引擎",
    "客户生命周期价值(CLV)预测:预估客户长期价值,优化营销预算分配",
    "A/B测试框架:设计实验验证营销策略的有效性",
    "自然语言处理:如果获取到评论数据,可进行情感分析"
]

print("\n" + "="*60)
print("未来研究方向")
print("="*60)
for item in future_work:
    print(f"  • {item}")
1
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')
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 5.8 案例总结

本案例基于Kaggle平台的真实电商数据集,完整演示了数据分析的全流程:

  1. 数据获取:使用Kaggle API下载真实数据集
  2. 数据清洗:处理数据类型、添加衍生特征、检测异常值
  3. 探索性分析:从客户画像、产品表现、设备行为、客户忠诚度等多个维度深入分析
  4. 高级分析:相关性分析、客户聚类分群、基础预测建模
  5. 可视化呈现:综合使用Matplotlib、Seaborn和Pyecharts创建静态与交互式图表
  6. 商业洞察:基于数据得出 actionable 的商业建议

通过本案例,读者可以掌握处理真实数据集的能力,理解如何将数据分析技术应用于实际业务场景,并能够从数据中提炼有价值的商业洞察。

最近更新: 2026/6/25
 |  Copyright © -Present Kelsen All Rights Reserved. | Author KelsenJing
Python数据分析全栈教程:从环境搭建到Kaggle实战