一、引言
Python 是“胶水语言”,能够将各种不同的库和工具粘合在一起,创造出强大的解决方案。
在上一次的介绍中,我们已经讲解了 Python 基础的 4 个模块(数据结构、流程控制、函数用法、面向对象);本次将聚焦 Python 的进阶使用方法,以及数据分析领域的核心工具库。对于刚开始接触量化分析或数据分析初学者而言,最常用的两个基础工具库便是 NumPy 和 Pandas。
对于量化分析人员而言,NumPy 和 Pandas 是最核心、几乎不可或缺的基础工具库——它们分别从底层计算和数据处理两个维度,支撑了量化策略开发、数据清洗、回测分析等关键环节的高效实现。在我之前从事数据分析相关工作期间,Pandas 也是我最常用的工具库之一。
没有 Python 基础的朋友,建议先翻一翻上次的基础篇:
https://www.pandaai.online/community/article/203
咱们就正式开启 NumPy 和 Pandas 的学习吧!
二、Numpy
首先介绍NumPy。 Numpy 是 Numerical Python 的缩写,从其全称可看出它是 Python 专门处理数组 (array) 的计算的工具包,其官网地址是 https://numpy.org/。
# 在使用NumPy包之前,需要引进它并检查它的版本
import numpy
numpy.__version__
根据上面的代码可以用 numpy 里面所有的内置方法 (build-in methods) 了,比如求 numpy.sum()
与均值 numpy.mean()
。通常给 numpy 起个别名 np,语法如下:
import numpy as np
这样所有出现 numpy 的地方都可以用 np 替代,上面的求和、均值可写 np.sum()
和 np.mean()
。
下面将使用NumPy从创建数组开始,介绍数组的保存与加载、数组的索引和切片、数组的变形以及数据的计算等操作。
(一)Numpy简介
NumPy数组是由相同类型元素组成的集合构成的数据结构。在NumPy数组中,数值型元素的使用频率最高;通常情况下,一维数组、二维数组、三维数组分别对应“线”“面”“体”的空间概念。
- 一维数组:类似“一条线”(例如一列连续的数字),仅对应单一维度的轴(axis)方向。
- 二维数组:类似“一张平面表格”(包含多行多列数据),对应行、列两个维度的轴方向。
- 三维数组:类似“一叠平面表格”(由多个二维数组堆叠而成),对应行、列、层三个维度的轴方向。
从计算机底层逻辑的严格定义来看:数组本质上是计算机内存中一块连续的一维内存块(1D memory),其数据结构由一套索引方案(indexing scheme)和数据类型(data type)共同定义。
因此,NumPy数组的核心构成可概括为:
NumPy 数组 = 内存块 + 索引方案 + 数据类型
其中:
- 内存块:存储数组的原始数据,是数组的“数据载体”;
- 索引方案:用于定位数组中任意元素的位置,是数组的“定位规则”;
- 数据类型:描述数组中元素的具体类型(如整数、浮点数等),是数组的“数据属性说明”。
为什么要专门学习数组?
看下面数组和列表之间的计算效率对比:两者的大小均为 100 万,将每个元素乘以 10,重复运行 20 次,并使用魔法命令 %time
记录时间。
从上述结果可以发现,数组的计算效率约为列表的 14 倍。需要注意的是,每次运行的具体时间可能不完全相同,但两者的效率差距始终保持在 10~15 倍 左右。
由此可见,若元素全部为数值型变量,数组是一种非常高效的数据结构。因此,我们非常有必要学习数组的使用。
(二)创建数值型和结构数组
1、创建 NumPy 数组
创建 NumPy 数组有三种方式:
- 基础构造法:将函数 np.array()用在列表和元组上。
- 规则生成法:用函数 np.arange() 和 np.linspace() 创建。
- 快速初始化法:用函数 np.ones(), np.zeros(), np.eye() 和np.random.random()创建。
# 用「列表」和「元组」当“原材料”,用基础构造法的 np.array() 将它们转换成 numpy 数组。
list1 = [3, 5, 2, 8, 4]
tuple1 = (4, 8, 2, 5, 3)
arrL = np.array(list1)
arrT = np.array(tuple1)
print("arrL =",arrL)
print("arrT =",arrT)
# 规则生成法
# arange() 函数有三个参数,分别为起点、终点,、间隔
print(np.arange(10))
print(np.arange(1,10))
print(np.arange(1,10,2))
# linspace() 函数有三个参数,分别为起点、终点、点数
print(np.linspace(1,6,6))
print(np.linspace(3,8,11))
# 快速初始化法
#用 zeros() 创建元素全是 0 的数组
print(np.zeros(10))
#用 ones() 创建元素全是 1 的数组
print(np.ones((2,3)))
#用 random() 创建随机元素的数组
print(np.random.random((2,2)))
#用 eye() 创建对角线都是 1 其他元素都是 0 的二维数组
print(np.eye(4,3))
2、数组属性
我们可以使用dir(arr)
查看所有属性。关键属性包括:
type(arr)
:<class 'numpy.ndarray'>
arr.ndim
:维度数(轴数)。len(arr)
:第一维长度(仅一维有意义)。arr.size
:元素总数。arr.shape
:形状元组(各维大小)。arr.dtype
:元素类型(如float64
、int32
)。arr.itemsize
:每个元素字节数(如float64
为8)。arr.strides
:跨度元组(各维推进字节数,反映内存布局)。
# Example 1: One-dimensional array
L = [4.7, 6.2, 1.9, 7.3, 3.8]
arr = np.array(L)
print('The type is', type(arr)) # The type is <class 'numpy.ndarray'>
print('The dimension is', arr.ndim) # The dimension is 1
print('The length of array is', len(arr)) # The length of array is 5
print('The number of elements is', arr.size) # The number of elements is 5
print('The shape of array is', arr.shape) # The shape of array is (5,)
print('The type of elements is', arr.dtype) # The type of elements is float64
print('The item size of', arr.dtype, 'is', arr.itemsize) # The item size of float64 is 8
print('The stride of array is', arr.strides) # The stride of array is (8,)
# Example 2: Two-dimensional array
L2 = [[2, 4, 6], [8, 10, 12]]
arr2d = np.array(L2)
print(arr2d) # [[ 2 4 6]
# [ 8 10 12]]
print('The type is', type(arr2d)) # The type is <class 'numpy.ndarray'>
print('The dimension is', arr2d.ndim) # The dimension is 2
print('The length of array is', len(arr2d)) # The length of array is 2
print('The number of elements is', arr2d.size) # The number of elements is 6
print('The shape of array is', arr2d.shape) # The shape of array is (2, 3)
print('The type of elements is', arr2d.dtype) # The type of elements is int32
print('The item size of', arr2d.dtype, 'is', arr2d.itemsize) # The item size of int32 is 4
print('The stride of array is', arr2d.strides) # The stride of array is (12, 4)
# Example 3: Four-dimensional array
arr4d = np.random.random((2, 2, 2, 3))
print(arr4d) # [[[[random values between 0 and 1]]], ...] (specific values vary)
print('The type is', type(arr4d)) # The type is <class 'numpy.ndarray'>
print('The dimension is', arr4d.ndim) # The dimension is 4
print('The length of array is', len(arr4d)) # The length of array is 2
print('The number of elements is', arr4d.size) # The number of elements is 24
print('The stride of array is', arr4d.strides) # The stride of array is (96, 48, 24, 8)
print('The shape of array is', arr4d.shape) # The shape of array is (2, 2, 2, 3)
print('The type of elements is', arr4d.dtype) # The type of elements is float64
3、结构数组(Structured Array)
Numpy中还有一种特殊的数组叫做结构数组 (structured array),它最大的特点就是数组中的元素类型不同,因此创建结构数组的关键在于如何创建这个元素类型,即 dtype
。
# 定义一个结构化数组的 dtype,包含不同列类型
dt = np.dtype([('企业', 'S12'), ('代码', 'S6'), ('价格/股数', 'f4', 2)])
# 使用定义的 dtype 创建一个结构化数组
s = np.array([('Tesla', 'TSLA', (250.3, 34567)),
('Facebook', 'META', (320.1, 45678))], dtype=dt)
# 将字节字符串解码为普通字符串
bstr = b'Tesla'
sstr = bstr.decode("utf-8")
print(type(bstr), bstr) # <class 'bytes'> b'Tesla'
print(type(sstr), sstr) # <class 'str'> Tesla
# 访问 '企业' 字段
s['企业'] # array([b'Tesla', b'Facebook'], dtype='|S12')
# 计算 '价格/股数' 中第一列值的均值
s['价格/股数'][:, 0].mean() # 285.2 (250.3 和 320.1 的平均值)
# 访问第二行的 '代码' 字段
s[1]['代码'] # b'META'
- dtype 定义:
dt
使用元组列表定义结构化数组的列类型。‘S12’ 表示长度为 12 的字符串,‘S6’ 表示长度为 6 的字符串,‘f4’ 表示 4 字节 (32 位) 浮点数,‘2’ 表示每行包含 2 个浮点值。 - 结构化数组创建:
s
包含两行数据,每行有企业名称、代码和一个 (价格, 股数) 元组。数据类型由dt
控制。 - 字节解码:
bstr
是一个字节字符串,sstr
通过decode("utf-8")
转换为普通字符串,展示类型转换过程。 - 字段访问:
s['企业']
返回企业名称数组,s['价格/股数'][:, 0].mean()
计算价格列均值,s[1]['代码']
获取第二行代码。 - 结果注释:均值 285.2 是 (250.3 + 320.1) / 2,具体值随输入变化,但逻辑一致。
(三)数组的保存和存载
下面介绍NumPy数组的保存与加载方法,并以 .npy
、.txt
、.csv
三种格式为例详细说明:
- 自身
.npy
格式
- 保存:使用
np.save(npy_file, arr)
函数,将NumPy数组保存为.npy
格式文件。 - 加载:使用
np.load(npy_file)
函数,加载已保存的.npy
格式数组。 - 优势:适合NumPy内部使用,加载快速,无需额外参数。
- 文本
.txt
格式
- 保存:通过
np.savetxt(txt_file, arr)
函数,把数组保存为.txt
文本格式。 - 加载:利用
np.loadtxt(txt_file)
函数,加载.txt
格式的数组。 - 注意:默认使用空格分隔,适合简单数值数据。
- 文本
.csv
格式
- 加载:假设CSV文件中元素以分号
;
分隔,可使用np.genfromtxt(csv_file, delimiter=';')
加载(需指定分隔符delimiter
,否则可能因识别不到数字而显示nan
)。 - 注意:适合分隔符自定义的逗号分隔值文件,常见于数据交换。
# Create and save a one-dimensional array
arr = np.arange(10)
np.save('data_np', arr)
print(arr) # [0 1 2 3 4 5 6 7 8 9]
# Load the saved array
arr_loaded = np.load('data_np.npy') #可以指定本地路径
print(arr_loaded) # [0 1 2 3 4 5 6 7 8 9]
# Create and save a two-dimensional array as text
lst = [[7, 8, 9], [10, 11, 12]]
arr = np.array(lst)
np.savetxt('data_text', arr)
arr_loaded = np.loadtxt('data_text.txt')
print(arr_loaded) # [ 7. 8. 9. 10. 11. 12.]
#假设已经在 csv 文件里写进去了 [[1,2,3], [4,5,6]],每行的元素是由「分号 ;」来分隔的。
# Load data from a CSV file with default and custom delimiter
arr_loaded = np.genfromtxt('data_csv.csv')
print(arr_loaded) # [[nan nan] [nan nan]] (if no valid data or delimiter mismatch)
arr_loaded = np.genfromtxt('data_csv.csv', delimiter=',')
print(arr_loaded) # [[ 1. 2.] [ 3. 4.]] (assuming data like "1,2\n3,4" in file)
(四)数组的获取
NumPy 数组提供三种核心访问方式:普通索引(normal indexing)、布尔索引(boolean indexing) 和 花式索引(fancy indexing)。下面我们将从一维数组出发,逐步扩展到多维数组场景,重点解析 索引与切片的区别、视图机制原理,并结合实际应用场景说明其用法。
1. 普通获取(Normal Indexing)
NumPy 数组的基础访问方式通过 索引(获取单个元素) 和 切片(获取子数组) 实现,支持一维到多维扩展,核心分析点围绕 维度处理、视图机制、多维扩展规则 展开。
- 一维数组:
- 创建示例:
arr = np.array([0, 1, 2, 3, 4])
。 - 索引:从0开始计数。单个元素如
arr[3]
返回3;多个元素如arr[[1,2,3]]
返回[1, 2, 3]
。 - 切片:含头不含尾,如
arr[1:4]
返回[1, 2, 3]
;arr[:]
返回整个数组。 - 视图 vs. 复制:NumPy切片返回视图(view),修改切片会影响原数组(如
arr_slice = arr[:3]; arr_slice[0] = 100
会改变arr
)。而Python列表切片是复制(copy),不会影响原列表。下面图展示了这一区别,强调NumPy设计是为了大型数组的效率优化。如果需要复制,可用copy()
如arr_copy = arr[1:4].copy()
。
- 创建示例:
- 二维数组:
- 创建示例:
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
。 - 索引:
- 行状获取:
arr2d[2]
返回[7, 8, 9]
。 - 元素获取:
arr2d[0][2]
或arr2d[0, 2]
返回3。前者多括号,后者单括号(更高效,尤其高维)。
- 行状获取:
- 切片:
- 行状:
arr2d[:2]
返回前两行[[1,2,3], [4,5,6]]
。 - 列状:
arr2d[:, [0,2]]
返回第一和第三列[[1,3], [4,6], [7,9]]
。 - 块状:
arr2d[:2, 2]
返回[3,6]
(降维);arr2d[1:2, :2]
返回[[4,5]]
(保持维度)。
- 行状:
- 维度保持规律:索引降维,切片保持维度。
- 创建示例:
- 三维数组(N维扩展):
- 创建示例:
arr3d = np.arange(24).reshape((3,2,4))
(形状(3,2,4))。 - 索引:指定每个轴的位置,如
arr3d[1,1,2]
返回14。 - 切片一维:沿单轴切片,如
arr3d[2,0,1]
返回17;需用:
表示其他轴全取。 - 切片二维:沿两轴切片,如
arr3d[1]
(等价arr3d[1,:,:]
)返回二维子数组。 - 分析:高维时,默认省略后轴的
:
,但需显式添加以避免混淆。强调N维通用性:代码范式arr[a,b,c,...]
,a/b/c可为索引/切片/列表。
- 创建示例:
普通获取的分析点:高效但需注意视图修改原数组;多维时,逗号分隔轴,便于扩展。
# 创建一个一维数组
arr = np.array([10, 20, 30, 40, 50])
# 索引单个元素
arr[2] # 30 (第三个元素,从0开始计数)
# 索引多个元素
arr[[1, 3]] # [20 40]
# 切片获取子数组
arr[0:3] # [10 20 30] (前三个元素,含头不含尾)
# 演示列表切片 vs 数组切片
l = [10, 20, 30, 40, 50]
l_slice = l[1:4]
l_slice[0] = 999
print(l_slice) # [999, 30, 40]
print(l) # [10, 20, 30, 40, 50] (修改切片不影响原列表)
arr_slice = arr[1:4]
arr_slice[0] = 999
print(arr_slice) # [999 30 40]
print(arr) # [10 999 30 40 50] (修改切片影响原数组)
# 使用 copy() 创建独立副本
arr_copy = arr[0:3].copy()
arr_copy[1] = 888
print(arr) # [10 999 30 40 50] (原数组未变)
print(arr_copy) # [10 888 30]
# 创建一个二维数组
arr2d = np.array([[3, 6, 9], [12, 15, 18], [21, 24, 27]])
print(arr2d) # [[ 3 6 9]
# [12 15 18]
# [21 24 27]]
# 行状索引
arr2d[0] # [3 6 9] (第一行)
# 元素索引
arr2d[1][2] # 18 (第二行第三列)
arr2d[1, 2] # 18 (等价写法)
# 行状切片
arr2d[1:] # [[12 15 18]
# [21 24 27]] (第二行到最后)
# 列状切片
arr2d[:, [1]] # [[ 6]
# [15]
# [24]] (第二列)
# 块状切片
arr2d[0:2, 1:] # [[ 6 9]
# [15 18]] (前两行后两列)
# 维度保持对比
print(arr2d[2], arr2d[2].shape) # [21 24 27] (3,) (降维)
print(arr2d[2, :], arr2d[2, :].shape) # [21 24 27] (3,) (降维)
print(arr2d[2:3, :], arr2d[2:3, :].shape) # [[21 24 27]] (1, 3) (保持维度)
# 创建一个三维数组
arr3d = np.arange(18).reshape((3, 3, 2))
print(arr3d) # [[[ 0 1]
# [ 2 3]
# [ 4 5]]
# [[ 6 7]
# [ 8 9]
# [10 11]]
# [[12 13]
# [14 15]
# [16 17]]]
# 元素获取
arr3d[0, 2, 1] # 5 (第一层第三行第二列)
arr3d[2, 1, 0] # 14 (第三层第二行第一列)
# 沿着轴 2 切片
arr3d[2, 0] # [12 13] (等价于 arr3d[2, 0, :])
# 沿着轴 1 切片
arr3d[1, :, 1] # [ 7 9 11] (第二层所有行第二列)
# 沿着轴 0 切片
arr3d[:, 2, 0] # [ 4 10 16] (所有层第三行第一列)
# 沿着轴 1 和 2 切片
arr3d[0] # [[0 1]
# [2 3]
# [4 5]] (等价于 arr3d[0, :, :])
# 沿着轴 0 和 2 切片
arr3d[:, 1] # [[ 2 3]
# [ 8 9]
# [14 15]] (等价于 arr3d[:, 1, :])
# 沿着轴 0 和 1 切片
arr3d[:, :, 0] # [[ 0 2 4]
# [ 6 8 10]
# [12 14 16]] (所有层所有行第一列)
2. 布尔获取(Boolean Indexing)
用布尔数组(True/False组成)作为索引,筛选符合条件的元素。适合数据过滤,如股票分析。布尔获取就是用一个由布尔类型值组成的数组来获取元素的方法。
import numpy as np
# 创建股票代码数组
codes = np.array(['AAPL', 'GOOG', 'AMZN', 'AAPL', 'AMZN', 'GOOG'])
# 创建股票价格数组(每行:开盘价, 最高价, 收盘价)
prices = np.array([[150, 152, 151], [2800, 2820, 2810], [3000, 3020, 3010],
[155, 157, 156], [3050, 3070, 3060], [2850, 2870, 2860]])
print(prices)
# [[ 150 152 151]
# [2800 2820 2810]
# [3000 3020 3010]
# [ 155 157 156]
# [3050 3070 3060]
# [2850 2870 2860]]
# 生成布尔数组,筛选 'AAPL' 的行
code_aapl = codes == 'AAPL'
print(code_aapl) # [ True False False True False False]
# 使用布尔索引获取 'AAPL' 的最高价和收盘价(第2列和第3列)
prices[code_aapl, 1:] # [[152 151]
# [157 156]]
# 组合条件:筛选 'GOOG' 或 'AMZN' 的行
cond = (codes == 'GOOG') | (codes == 'AMZN')
print(cond) # [False True True False True True]
# 使用布尔索引获取符合条件的行
prices[cond] # [[2800 2820 2810]
# [3000 3020 3010]
# [3050 3070 3060]
# [2850 2870 2860]]
# 使用布尔索引进行赋值:将收盘价低于 2000 的值设为 0
prices[prices[:, 2] < 2000] = 0
print(prices) # [[ 0 0 0]
# [2800 2820 2810]
# [3000 3020 3010]
# [ 0 0 0]
# [3050 3070 3060]
# [2850 2870 2860]]
3. 花式获取(Fancy Indexing)
用整数列表/数组指定特定顺序的位置,灵活获取非连续元素。花式索引是用获取数组中特定元素的有效方法。
# 创建一个 6x5 的二维数组
arr = np.arange(30).reshape((6, 5))
print(arr) # [[ 0 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]]
# 花式索引指定行顺序
arr[[3, 1, 5, 0]] # [[15 16 17 18 19]
# [ 5 6 7 8 9]
# [25 26 27 28 29]
# [ 0 1 2 3 4]]
# 使用负索引从末尾选择行
arr[[-2, -4, -1]] # [[20 21 22 23 24]
# [10 11 12 13 14]
# [25 26 27 28 29]]
# 多维花式索引,选择特定位置的元素
arr[[0, 2, 4, 5], [1, 3, 2, 4]] # [ 1 13 22 29]
# 结合切片和花式索引,重排列
arr[[0, 2, 4, 5]][:, [0, 4, 2, 1, 3]] # [[ 0 4 2 1 3]
# [10 14 12 11 13]
# [20 24 22 21 23]
# [25 29 27 26 28]]
# 使用 take 函数沿轴 0 获取指定行
np.take(arr, [1, 3], axis=0) # [[ 5 6 7 8 9]
# [15 16 17 18 19]]
# 使用 take 函数沿轴 1 获取指定列
np.take(arr, [0, 2, 4], axis=1) # [[ 0 2 4]
# [ 5 7 9]
# [10 12 14]
# [15 17 19]
# [20 22 24]
# [25 27 29]]
# 使用 put 函数替换指定索引的值
np.put(arr, [0, 10], -5) # 原数组第一个元素和第十一个元素被替换为 -5
print(arr) # [[-5 1 2 3 4]
# [ 5 6 7 8 9]
# [-5 11 12 13 14]
# [15 16 17 18 19]
# [20 21 22 23 24]
# [25 26 27 28 29]]
# 使用 put 函数并 clip 模式处理越界索引
np.put(arr, 35, -10, mode='clip') # 索引 35 越界,clip 到最后一个元素
print(arr) # [[-5 1 2 3 4]
# [ 5 6 7 8 9]
# [-5 11 12 13 14]
# [15 16 17 18 19]
# [20 21 22 23 24]
# [25 26 27 28 -10]]
(五)数组的变形
下面将介绍 NumPy 数组的五种核心变形操作:重塑与打平、合并与分裂、重复与拼接、排序与增减、视图与复制。内容从基础概念出发,逐步延伸至多维数组场景,重点解析各操作的内存机制、视图与复制的区别,并结合代码示例与解析说明其实际应用。分析聚焦于操作的底层机制、参数影响及典型场景(如数据重构与效率优化),同时提示潜在使用陷阱,突出实用价值。
1. 重塑和打平
这部分解释了维度变换操作,仅改变视图而不修改底层数据。重塑(reshape)从低维到高维,打平(flatten/ravel)从高维到低维。关键依赖内存布局(行主序/列主序)。
- 重塑(reshape) - 从低维到高维:
- 使用
np.reshape(shape)
或arr.reshape(shape)
,shape为元组。示例:一维数组arr = np.arange(1,13)
重塑为(4,3)
得到[[1 2 3] [4 5 6] [7 8 9] [10 11 12]]
(行主序填充)。 - 支持
1
自动计算维度,如arr.reshape((2,-1))
输出(2,6)
数组。 - 问题解答:默认行主序(order=‘C’),元素按行填充;若order=‘F’(列主序),则按列填充,得到
[[1 5 9] [2 6 10] [3 7 11] [4 8 12]]
。 - 分析:重塑是视图操作,不复制数据,便于多维数据重组。但需确保总元素数不变,否则报错。高维扩展:适用于N维数组,order参数控制填充方向。
- 使用
- 打平(ravel/flatten) - 从高维到低维:
arr.ravel()
或arr.flatten()
,默认行主序打平。示例:二维数组打平为[1 2 3 4 5 6 7 8 9 10 11 12]
。- order='F’打平为
[1 4 7 10 2 5 8 11 3 6 9 12]
。 - 区别:
ravel()
默认不复制(视图),仅order='F’时复制;flatten()
总是复制。通过修改打平数组验证:ravel©修改原数组,ravel(F)/flatten不改。 - 分析:视图机制提升效率,但风险是意外修改原数据。高维时,order影响填充顺序,适用于数据扁平化如机器学习输入。
# 创建一个一维数组
arr = np.arange(9) # [0 1 2 3 4 5 6 7 8]
# 重塑为一维到二维 (3x3),默认行主序 (order='C')
arr.reshape((3, 3)) # [[0 1 2]
# [3 4 5]
# [6 7 8]]
# 重塑为二维,使用列主序 (order='F')
arr.reshape((3, 3), order='F') # [[0 3 6]
# [1 4 7]
# [2 5 8]]
# 使用 -1 自动计算维度
arr.reshape((3, -1)) # [[0 1 2]
# [3 4 5]
# [6 7 8]]
# 创建一个二维数组
arr2d = np.arange(1, 10).reshape((3, 3)) # [[1 2 3]
# [4 5 6]
# [7 8 9]]
# 打平为二维到一维,默认行主序 (order='C')
arr2d.ravel() # [1 2 3 4 5 6 7 8 9]
# 打平,使用列主序 (order='F')
arr2d.ravel(order='F') # [1 4 7 2 5 8 3 6 9]
# 使用 flatten() 打平,默认行主序 (总是复制)
arr2d.flatten() # [1 2 3 4 5 6 7 8 9]
# 演示 ravel() 和 flatten() 的视图 vs 复制区别
arr_ravel = arr2d.ravel() # 视图
arr_ravel[0] = 100
print(arr2d) # [[100 2 3]
# [ 4 5 6]
# [ 7 8 9]] (修改 ravel 影响原数组)
arr_flatten = arr2d.flatten() # 复制
arr_flatten[1] = 200
print(arr2d) # [[100 2 3]
# [ 4 5 6]
# [ 7 8 9]] (修改 flatten 不影响原数组)
# ravel() 使用 order='F' (复制)
arr_ravel_f = arr2d.ravel(order='F')
arr_ravel_f[2] = 300
print(arr2d) # [[100 2 3]
# [ 4 5 6]
# [ 7 8 9]] (修改 ravel F 不影响原数组)
2. 合并和分裂
这部分聚焦数组的分合操作,改变维度或结构,但保持元素。
- 合并(stack) - 多合一:
- 三种方式:通用
np.concatenate(arrs, axis)
;专用vstack/hstack/dstack
;极简r_/c_
。 - 示例:两个
(2,3)
数组,按axis=0(vstack)合并为(4,3)
;axis=1(hstack)为(2,6)
;dstack为(2,3,2)
(新增深度轴)。 - 三种合并:vstack竖堆、hstack水平并、dstack深度叠。分析dstack结果
(2,3,2)
而非直观(2,2,3)
,因轴优先级(轴2新增)。 r_/c_
:r_按行、c_按列;支持切片/列表合并,如np.r_[-2:2:1, [0]*3, 5, 6]
。高级形式’a,b,c’控制轴/最小维/升维,如’0,2,0’升维后轴0合并。强调灵活性但语法复杂。- 分析:合并需形状兼容(除合并轴外)。适用于数据连接,如合并数据集。dstack增加维度,适合图像通道。
- 三种方式:通用
- 分裂(split) - 一分多:
- 通用
np.split(arr, indices_or_sections, axis)
;专用vsplit/hsplit
。 - 示例:
(5,5)
数组,按[1,3]分裂为三部分(:1,1:3,3:)。默认axis=0(vsplit竖分);axis=1(hsplit水平分)。 - 对比:vsplit分行、hsplit分列。indices为切片点,sections为等分数。
- 分析:分裂返回列表视图,便于数据分区如训练/测试集。需确保可分,否则报错。专用函数简化语法。
- 通用
# 创建两个 3x2 的数组
a = np.array([[1, 2], [3, 4], [5, 6]])
b = np.array([[7, 8], [9, 10], [11, 12]])
# 通用合并:沿轴 0 合并 (垂直堆叠)
np.concatenate([a, b], axis=0) # [[ 1 2]
# [ 3 4]
# [ 5 6]
# [ 7 8]
# [ 9 10]
# [11 12]]
# 专用垂直堆叠 vstack
np.vstack([a, b]) # [[ 1 2]
# [ 3 4]
# [ 5 6]
# [ 7 8]
# [ 9 10]
# [11 12]]
# 通用合并:沿轴 1 合并 (水平并列)
np.concatenate([a, b], axis=1) # [[ 1 2 7 8]
# [ 3 4 9 10]
# [ 5 6 11 12]]
# 专用水平并列 hstack
np.hstack([a, b]) # [[ 1 2 7 8]
# [ 3 4 9 10]
# [ 5 6 11 12]]
# 专用深度叠加 dstack (沿轴 2 合并)
np.dstack([a, b]) # [[[ 1 7]
# [ 2 8]]
# [[ 3 9]
# [ 4 10]]
# [[ 5 11]
# [ 6 12]]]
# 极简 r_ 沿轴 0 合并
np.r_[a, b] # [[ 1 2]
# [ 3 4]
# [ 5 6]
# [ 7 8]
# [ 9 10]
# [11 12]]
# 极简 c_ 沿轴 1 合并
np.c_[a, b] # [[ 1 2 7 8]
# [ 3 4 9 10]
# [ 5 6 11 12]]
# 创建一个 4x4 的数组用于分裂
arr = np.arange(16).reshape(4, 4)
print(arr) # [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]
# [12 13 14 15]]
# 通用分裂:沿轴 0 分成两部分
np.split(arr, [2], axis=0) # [array([[0, 1, 2, 3],
# [4, 5, 6, 7]]),
# array([[ 8, 9, 10, 11],
# [12, 13, 14, 15]])]
# 专用 vsplit 垂直分裂
np.vsplit(arr, [2]) # [array([[0, 1, 2, 3],
# [4, 5, 6, 7]]),
# array([[ 8, 9, 10, 11],
# [12, 13, 14, 15]])]
# 通用分裂:沿轴 1 分成两部分
np.split(arr, [2], axis=1) # [array([[ 0, 1],
# [ 4, 5],
# [ 8, 9],
# [12, 13]]),
# array([[ 2, 3],
# [ 6, 7],
# [10, 11],
# [14, 15]])]
# 专用 hsplit 水平分裂
np.hsplit(arr, [2]) # [array([[ 0, 1],
# [ 4, 5],
# [ 8, 9],
# [12, 13]]),
# array([[ 2, 3],
# [ 6, 7],
# [10, 11],
# [14, 15]])]
3. 重复和拼接
本质是复制操作,元素级(repeat) vs. 数组级(tile)。
- 重复(repeat) - 元素层面复制:
arr.repeat(repeats, axis)
。一维:整数/列表控制重复次数,如[0 1 2].repeat(3)
为[0 0 0 1 1 1 2 2 2]
。- 多维:加axis,如repeat(2, axis=0)行重复。列表指定每个元素重复。
- 分析:扩展维度,适用于数据增强。无axis时扁平化重复。
- 拼接(tile) - 数组层面复制:
np.tile(arr, reps)
。整数:列重复,如tile(arr2d, 2)为水平复制。- 元组:形状复制,如(2,3)为2行3列 tiling,形成更大网格。
- 分析:视arr为“瓷砖”,reps为铺设模式。高维如(2,3,4)创建3D网格。
# 创建一个 2x3 的数组
arr = np.array([[1, 2, 3], [4, 5, 6]])
# 使用 repeat 沿指定轴重复数组元素
np.repeat(arr, 2, axis=0) # [[1 2 3]
# [1 2 3]
# [4 5 6]
# [4 5 6]]
# 使用 repeat 沿轴 1 重复
np.repeat(arr, 3, axis=1) # [[1 1 1 2 2 2 3 3 3]
# [4 4 4 5 5 5 6 6 6]]
# 使用 tile 重复整个数组
np.tile(arr, (2, 1)) # [[1 2 3]
# [4 5 6]
# [1 2 3]
# [4 5 6]]
# 使用 tile 重复并扩展列
np.tile(arr, (1, 2)) # [[1 2 3 1 2 3]
# [4 5 6 4 5 6]]
# 创建两个数组用于拼接
a = np.array([10, 20, 30])
b = np.array([40, 50, 60])
# 使用 append 拼接一维数组
np.append(a, b) # [10 20 30 40 50 60]
# 使用 column_stack 垂直拼接为一列
np.column_stack((a, b)) # [[10 40]
# [20 50]
# [30 60]]
# 使用 row_stack 水平拼接
np.row_stack((a, b)) # [[10 20 30]
# [40 50 60]]
# 创建一个 2x3 数组用于重复操作
arr2 = np.array([[7, 8, 9], [10, 11, 12]])
# 使用 repeat 重复特定元素
np.repeat(arr2, [1, 2], axis=0) # [[ 7 8 9]
# [10 11 12]
# [10 11 12]]
# 使用 tile 重复特定次数
np.tile(arr2, (2, 3)) # [[ 7 8 9 7 8 9 7 8 9]
# [10 11 12 10 11 12 10 11 12]]
4. 排序和增减
排序分直接/间接,增减类似列表操作。
- 排序:
- 直接排序(sort):
arr.sort(axis)
原地升序。np.sort(arr)复制版。axis=0跨行、1跨列。示例:随机数组排序前后比较。 - 无降序参数,用[::-1]反转。分析:原地高效,但永久修改;复制版安全。
- 间接排序(argsort/lexsort):argsort返回索引,如[100 60 99 80 91].argsort()为[1 3 4 2 0](位置排序)。
- lexsort多序列:后序列优先,如last_name+first_name排序。示例分析平手时前序列决胜。
- 分析:argsort用于排序键,lexsort如Excel多列排序。适用于数据排名/分组。
- 直接排序(sort):
- 增减:
np.insert(arr, obj, values)
前插;np.delete(arr, obj)
删除。obj为索引/列表。- 分析:返回新数组,不原地。简单但多维需指定轴。
import numpy as np
# 创建一个一维数组
arr = np.array([45, 12, 78, 23, 91, 34])
# 沿轴 0 排序一维数组
np.sort(arr) # [12 23 34 45 78 91]
# 获取排序后的索引
np.argsort(arr) # [1 3 5 0 2 4]
# 创建一个二维数组
arr2d = np.array([[5, 9, 2], [8, 1, 6], [4, 3, 7]])
# 沿轴 0 排序 (每列排序)
np.sort(arr2d, axis=0) # [[4 1 2]
# [5 3 6]
# [8 9 7]]
# 沿轴 1 排序 (每行排序)
np.sort(arr2d, axis=1) # [[2 5 9]
# [1 6 8]
# [3 4 7]]
# 使用 sort 方法修改原数组
arr2d.sort(axis=1)
print(arr2d) # [[2 5 9]
# [1 6 8]
# [3 4 7]]
# 增加新元素到一维数组
np.append(arr, [15, 60]) # [45 12 78 23 91 34 15 60]
# 在指定位置插入元素
np.insert(arr, 2, [25, 30]) # [45 12 25 30 78 23 91 34]
# 删除指定位置的元素
np.delete(arr, [0, 4]) # [12 78 23 34]
# 创建一个 3x3 数组用于增减操作
arr3d = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
# 沿轴 0 插入新行
np.insert(arr3d, 1, [15, 25, 35], axis=0) # [[10 20 30]
# [15 25 35]
# [40 50 60]
# [70 80 90]]
# 沿轴 1 插入新列
np.insert(arr3d, 1, [5, 15, 25], axis=1) # [[10 5 20 30]
# [40 15 50 60]
# [70 25 80 90]]
# 删除沿轴 0 的指定行
np.delete(arr3d, 1, axis=0) # [[10 20 30]
# [70 80 90]]
5. 视图和复制
arr.view()
:共享内存,修改影响原数组。arr.copy()
:深复制,独立。- 示例对比:view修改原,copy不改。
- 分析:视图节省内存,但风险高。适用于临时变换。
整体分析:本节强调NumPy变形的高效(视图优先),但需注意order/axis影响结果。图表可视化内存/操作,便于理解。潜在问题:形状不兼容、意外修改。适用于数据预处理。
# 创建一个一维数组
arr = np.array([15, 25, 35, 45, 55])
# 获取视图 (view)
view = arr[1:4]
view[0] = 100
print(view) # [100 35 45]
print(arr) # [ 15 100 35 45 55] (修改视图影响原数组)
# 获取复制 (copy)
copy = arr[0:3].copy()
copy[1] = 200
print(copy) # [ 15 200 35]
print(arr) # [ 15 100 35 45 55] (修改复制不影响原数组)
# 创建一个二维数组
arr2d = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
# 获取二维视图 (沿轴 0 切片)
view2d = arr2d[0:2, :]
view2d[0, 0] = 300
print(view2d) # [[300 20 30]
# [ 40 50 60]]
print(arr2d) # [[300 20 30]
# [ 40 50 60]
# [ 70 80 90]] (视图修改影响原数组)
# 获取二维复制
copy2d = arr2d[1:3, :].copy()
copy2d[0, 1] = 400
print(copy2d) # [[ 40 400 60]
# [ 70 80 90]]
print(arr2d) # [[300 20 30]
# [ 40 50 60]
# [ 70 80 90]] (复制修改不影响原数组)
# 使用 view 方法获取视图
view_method = arr2d.view()
view_method[1, 2] = 500
print(view_method) # [[300 20 30]
# [ 40 50 500]
# [ 70 80 90]]
print(arr2d) # [[300 20 30]
# [ 40 50 500]
# [ 70 80 90]] (view 方法也生成视图)
# 验证数据共享 (检查 base 属性)
print(view2d.base is arr2d) # True (视图共享原数组内存)
print(copy2d.base is None) # True (复制不共享内存)
(六)数组的计算
本节详细介绍了NumPy数组的四大类计算操作:元素层面计算(element-wise)、线性代数计算(linear algebra)、元素整合计算(aggregation)和广播机制计算(broadcasting)。
这些计算方式是强调NumPy计算的向量化(vectorization)优势,即无需循环即可并行处理元素,提高效率。
每个类别配有代码示例,突出数组 vs. 矩阵的区别、多维扩展以及广播规则的实用性。分析重点在于操作机制、参数影响(如axis、order)和潜在陷阱(如形状不兼容)。以下逐部分分析:
1. 元素层面计算(Element-Wise)
元素层面计算(Element-Wise)是 NumPy 的核心特性之一,通过向量化技术实现数组元素的并行操作,无需显式循环即可完成批量计算。
其操作类型主要包括 二元运算(如加减乘除)、函数运算(如倒数、平方、开方、三角函数、指数、对数等数学函数)和 比较运算(如大小比较及逻辑判断)。二元运算支持标量与数组的混合计算(如标量通过广播机制自动扩展为同形数组),返回新数组并适用于批量数据处理,但需注意形状不匹配导致的错误;函数运算依托 NumPy 提供的高效向量化数学函数(默认 float64 数据类型),计算效率显著高于 Python 循环,同时需关注整数除法等潜在精度问题;比较运算生成布尔数组,既可用于逻辑判断,也能作为掩码(masking)实现数据过滤,其中 np.allclose
方法通过设定误差阈值解决了浮点数比较的精度难题。此类操作是数据清洗与转换的基础工具,其向量化特性大幅提升了计算速度(如显著快于传统 for 循环)。
# 创建一个一维数组
arr = np.array([2, 4, 6, 8, 10])
# 逐元素加法
arr + 5 # [ 7 9 11 13 15]
# 逐元素乘法
arr * 2 # [ 4 8 12 16 20]
# 逐元素比较
arr > 6 # [False False False True True]
# 创建两个一维数组进行元素级运算
arr2 = np.array([1, 3, 5, 7, 9])
# 逐元素加法
arr + arr2 # [ 3 7 11 15 19]
# 逐元素除法
arr / arr2 # [2. 1.33333333 1.2 1.14285714 1.11111111]
# 创建一个二维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
# 逐元素平方
arr2d ** 2 # [[ 1 4 9]
# [16 25 36]]
# 逐元素逻辑与
(arr2d > 2) & (arr2d < 5) # [[False False False]
# [ True False False]]
# 逐元素应用自定义函数
def double(x):
return x * 2
np.vectorize(double)(arr2d) # [[ 2 4 6]
# [ 8 10 12]]
# 逐元素取模
arr2d % 2 # [[1 0 1]
# [0 1 0]]
2. 线性代数计算(Linear Algebra)
NumPy 的线性代数计算模块区分了 数组(多维通用) 与 矩阵(二维专用) 的操作逻辑,尽管矩阵是数组的子类,但其在转置、求逆、相乘等线性代数场景中进行了针对性优化。
核心操作包括 创建(数组通过 np.array
生成,矩阵通过 np.asmatrix
或 np.mat
创建)、转置(数组用 .T
或 .transpose()
,矩阵同理,高维数组需通过 axes
参数控制轴顺序)、求逆(仅适用于方阵,通过 np.linalg.inv
计算,非奇异矩阵会报错)、相乘(数组默认元素级乘法,矩阵乘法需通过 *
运算符(矩阵对象)或 np.dot
/@
/matmul
函数实现,高维场景下 np.dot
支持通用点乘规则)。
矩阵与数组的操作差异体现了 NumPy 在通用性与专用性之间的平衡,此类功能是连接 NumPy 与 SciPy 线性代数模块的桥梁,广泛应用于机器学习中的矩阵分解等任务,但需注意形状匹配(如矩阵乘法的维度兼容性)及奇异矩阵导致的计算错误。
# 创建两个二维数组(矩阵)
A = np.array([[2, 3], [4, 5]])
B = np.array([[1, 2], [3, 4]])
# 矩阵乘法
np.dot(A, B) # [[11 16]
# [19 28]]
# 矩阵乘法使用 @ 运算符
A @ B # [[11 16]
# [19 28]]
# 求矩阵的行列式
np.linalg.det(A) # -2.0
# 求矩阵的逆
np.linalg.inv(A) # [[-2.5 1.5]
# [ 2. -1. ]]
# 求特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eig(A)
print(eigenvalues) # [-0.46410162 7.46410162]
print(eigenvectors) # [[-0.57604932 -0.75863022]
# [ 0.81741556 -0.65157366]]
# 求矩阵的转置
A.T # [[2 4]
# [3 5]]
# 求迹(对角线元素之和)
np.trace(A) # 7
# 解线性方程组 Ax = b,b = [1, 2]
b = np.array([1, 2])
x = np.linalg.solve(A, b)
print(x) # [-1. 1.]
3. 元素整合计算(Aggregation)
元素整合计算(Aggregation)通过聚合函数(如求和、均值、标准差、极值等)沿指定轴(axis)或整体对数组元素进行统计分析,其中 轴(axis) 是理解高维数据聚合的关键概念(外层括号对应轴 0,逐内递增)。
操作涵盖基础统计函数(如 sum
/mean
/std
/var
/min
/max
)、位置函数(如 argmin
/argmax
定位极值索引)、累积函数(如 cumsum
/cumprod
实现累加/累乘),并支持 nan
版本(如 nansum
自动忽略缺失值)。高维数据聚合时,沿轴操作会降低维度(除非设置 keepdims=True
保持结构),而整体聚合(如无轴指定)则返回单一统计值。此类操作是统计分析的基础工具,其核心在于通过轴参数控制聚合方向,错误使用可能导致维度混淆,而 keepdims
则为链式操作提供了便利。
import numpy as np
# 创建一个一维数组
arr = np.array([3, 7, 1, 9, 4])
# 求数组的总和
np.sum(arr) # 24
# 求数组的最大值
np.max(arr) # 9
# 求数组的最小值
np.min(arr) # 1
# 求数组的平均值
np.mean(arr) # 4.8
# 求数组的方差
np.var(arr) # 8.16
# 创建一个二维数组
arr2d = np.array([[2, 5, 8], [1, 9, 3], [6, 4, 7]])
# 沿轴 0 求和 (列和)
np.sum(arr2d, axis=0) # [ 9 18 18]
# 沿轴 1 求均值 (行均值)
np.mean(arr2d, axis=1) # [5. 4.33333333 5.66666667]
# 沿轴 0 求最大值
np.max(arr2d, axis=0) # [6 9 8]
# 累计和 (沿整个数组)
np.cumsum(arr2d) # [ 2 7 15 16 25 28 34 38 45]
# 累计乘积 (沿整个数组)
np.cumprod(arr2d) # [ 2 10 80 80 720 2160 12960 51840 362880]
# 统计非零元素的个数
np.count_nonzero(arr2d) # 9
# 统计沿轴 0 的非零元素
np.count_nonzero(arr2d, axis=0) # [3 3 3]
4. 广播机制计算(Broadcasting)
广播机制(Broadcasting)是 NumPy 处理形状不同数组间元素操作的核心技术,通过自动扩展数组维度(从后向前检查,补 1 后维度相等或某一维为 1 即视为兼容)实现高效计算,最终形状取各维度最大值。
规则应用包括 标量与数组操作(标量自动扩展)、行/列广播(如 (4,3) 数组与 (3,) 或 (4,1) 数组运算时分别沿行或列扩展)、高维广播(如 (1,2,3) 与 (2,1,3) 运算生成 (2,2,3) 结果)及 赋值广播(如通过 np.newaxis
升维辅助扩展)。广播避免了显式循环,显著提升计算效率与内存利用率,但隐式扩展可能导致意外结果(如维度不匹配时报 ValueError
),建议通过显式升维(如 np.newaxis
)明确操作意图。该机制是 NumPy 批量操作的核心优势之一,适用于大规模数据的快速处理。
# 创建一个一维数组和一个标量
arr1 = np.array([1, 2, 3, 4])
scalar = 5
# 标量与一维数组的加法(广播)
arr1 + scalar # [6 7 8 9]
# 标量与一维数组的乘法(广播)
arr1 * scalar # [ 5 10 15 20]
# 创建一个二维数组和一维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
arr1d = np.array([10, 20, 30])
# 一维数组广播到二维(沿列方向)
arr2d + arr1d # [[11 22 33]
# [14 25 36]]
# 创建一个二维数组和一个不符合直接广播的数组
arr2d_new = np.array([[1, 2], [3, 4], [5, 6]])
arr1d_new = np.array([7, 8])
# 调整形状以启用广播
arr1d_new = arr1d_new[:, np.newaxis] # 转换为列向量 [[7], [8]]
arr2d_new + arr1d_new # [[ 8 9]
# [10 11]
# [12 13]]
# 验证广播维度兼容性
try:
arr2d + arr1d_new # 维度不匹配,抛出 ValueError
except ValueError as e:
print("Error:", e) # Error: operands could not be broadcast together with shapes (2,3) (2,1)
# 使用广播进行条件操作
mask = arr2d > 2
arr2d[mask] = 100
print(arr2d) # [[ 1 2 100]
# [100 100 100]]
本次系统介绍了 NumPy 工具库的核心功能,涵盖数组基础操作与高级应用:首先讲解数值型与结构数组的创建方法,以及 npy、txt 和 csv 三种格式的数组存载技术;接着深入探讨数组元素的多种索引与切片方式(包括普通、布尔和花式索引),以及数组形态变换(低维到高维的重塑与高维到低维的打平);随后阐述数组的组合与分割操作(多合一的合并与一分多的分裂)、数组复制与拼接技术(元素层面的复制与数组层面的拼接),以及数组元素的排序与增减处理;进一步介绍数组的视图与复制操作、元素层面的二元计算与函数操作(含比较计算)、线性代数相关操作(矩阵创建、转置、求逆与相乘)、基于不同轴或整体的元素整合方法,以及形状不同数组间的广播机制应用,全面覆盖从数组构建到复杂计算的全流程操作。
三、Pandas
下面我们开始讲Pandas, 也是数据分析时常用的Python库。Pandas 是 Panel data analysis 的缩写,从其全称可看出它是 Python 里处理数据分析 (data analysis) 的工具包,其官网链接是 https://pandas.pydata.org/。
import pandas
pandas.__version__
# ,通常给 pandas起个别名 pd
import pandas as pd
pd.Series()
pd.DataFrame()
下面我们将使用Pandas从创建表格开始,介绍数据的保存与加载、数据获取、数据合并、数据重塑、数据分析、可视化以及数据预处理等相关操作。
(一)介绍Pandas
Pandas 里的数据结构(比如表格)本质上是 “带标签的数组”,可以理解为 NumPy 数组的升级版。
- NumPy 数组:只存纯数字,没有“这一列是啥意思”“这一行是谁”的说明(比如一个二维数组,你只知道是一堆数,但不知道哪列是股价、哪行是日期)。
- Pandas 数据结构:在 NumPy 数组的基础上,加了“描述信息”(比如行名、列名),让数据更易懂(比如表格里直接标着“平安银行”“茅台”,列名写着“2024-1-3 到 2025-1-3 的股价”)。
具体对应关系:
- 一维数据(序列/Series) = 一维数组(比如一列数) + 行索引(比如这列数是哪个股票的价格)
- 二维数据(数据帧/DataFrame) = 二维数组(比如多行多列的表格) + 行索引(比如每行是哪一天) + 列索引(比如每列是哪个股票)
- 三维数据(面板/Panel) = 三维数组(比如多个二维表格叠一起) + 行/列/项索引(但 Pandas 1.0 后不用了,用多层索引的 DataFrame 代替)
重点区别:Pandas 是为**表格类杂数据(比如股价、姓名、日期混在一起)设计的,NumPy 是为纯数字的同类型数据(比如全是整数或小数)**设计的(除非用 NumPy 的特殊结构数组)。
(二)创建Series & DataFrame
Pandas中有两大核心数据结构——Series(序列,一维)和DataFrame(数据帧,二维)的创建方法。内容从基础语法入手,逐步扩展到参数设置、属性访问和简单操作,强调Pandas数据结构的灵活性和信息完整性。每个创建方式配有代码示例、输出结果和解释,突出与NumPy数组的相似性(如values返回数组),但Pandas更注重标签(index/columns)和名称(name)的语义化。分析重点在于输入类型(列表/数组/字典)的处理、缺失值(NaN)的引入、维度扩展(MultiIndex),以及查看函数(head/tail/describe/info)的实用性。以下逐部分分析:
- 数据结构分类:根据维度划分,Series为一维(类似Python列表或NumPy一维数组),DataFrame为二维(类似Excel sheet或R data.frame)。DataFrame被描述为Series的容器(container),即每一列是一个Series。
- 分析:这一介绍桥接了3.1节的“数据表=数组+描述”公式,强调Pandas的异质性(可混类型数据)。未提及三维Panel(已废弃),焦点在实用二维数据上。教学点:初学者易将Series视为增强列表,DataFrame视为表格,代码简洁,便于上手。
1. Series创建(一维序列)
-
基本语法:
pd.Series(x, index=idx)
,x为数据值(位置参数),index为行标签(默认range(0, len(x))),可为整数/字符串/时间对象。- 分析:语法简明,index的默认值确保兼容NumPy,但显式设置提升可读性(如日期标签)。添加
name
参数(如’s2.name = ‘海底捞股价’’)使Series更完整,类似于数据列的标题。
- 分析:语法简明,index的默认值确保兼容NumPy,但显式设置提升可读性(如日期标签)。添加
-
用列表创建:
- 示例:
s = pd.Series([27.2, 27.65, 27.70, 28])
,输出带默认index(0-3)的float64 Series。访问s.values
返回数组,s.index
返回RangeIndex。 - 改进:用
pd.date_range('20190401', periods=4)
生成日期index,创建s2
,频率Freq=D(每日)。 - 操作示例:
- 索引:
s2[0]
或s2['2019-04-01']
(位置/标签)。 - 切片:
s2[1:3]
(前闭后开)或s2['2019-04-01':'2019-04-02']
(前闭后闭),返回子Series(含标签)。 - 布尔筛选:
s2[s2 > 27.5]
返回符合条件的子Series。 - 广播/元素运算:
s2 * 2 + 3
或np.log(s2)
,保留index。
- 索引:
- 分析:示例数据为“海底捞股价”(2019-04-01到04-04),真实场景化,便于理解时间序列。操作演示了Pandas的向量化(vectorized),效率高于循环。潜在陷阱:切片返回子Series而非列表,修改需小心视图机制(view)。
- 示例:
-
用数组创建:
- 示例:
s = pd.Series(np.array([27.2, 27.65, 27.70, 28, 28, np.nan]))
。 - 属性分析:
len(s)
:6(总元素,包括NaN)。s.shape
:(6,)(形状元组)。s.count()
:5(非NaN元素)。s.unique()
:返回唯一值数组(含NaN)。s.value_counts()
:频次统计(忽略NaN)。
- 分析:引入NaN演示缺失值处理,强调Pandas的鲁棒性。unique/value_counts实用(如频次分析),但NaN不计入count/value_counts,需注意数据清洗场景。
- 示例:
-
用字典创建:
- 示例:
data = {'BABA': 187.07, 'PDD': 21.83, 'JD': 30.79, 'BIDU': 184.77}
,s3 = pd.Series(data, name='中概股')
,键→index,值→data。添加s3.index.name = '股票代号'
。 - 自定义index:
s4 = pd.Series(data, index=['FB', 'BABA', 'PDD', 'JD'])
,无键处为NaN。 - 加法:
s3 + s4
,自动对齐index,缺失→NaN。 - 分析:字典创建自然映射键值对,适合标签语义强的场景(如股票代码)。对齐机制(alignment)是Pandas亮点,避免手动匹配。示例为“中概股”(BABA等),贴合金融读者。陷阱:自定义index引入NaN,需后续处理(如fillna)。
import pandas as pd # 创建一个 Series,包含股票代码和价格数据,指定索引 data = [150.5, 2800.0, 3050.0] stocks = ['TSLA', 'GOOGL', 'AMZN'] series = pd.Series(data, index=stocks) # 显示 Series print(series) # TSLA 150.5 # GOOGL 2800.0 # AMZN 3050.0 # dtype: float64 # 通过索引访问单个值 series['GOOGL'] # 2800.0 # 通过索引访问多个值 series[['TSLA', 'AMZN']] # TSLA 150.5 # AMZN 3050.0 # dtype: float64 # 使用布尔索引筛选 series[series > 1000] # GOOGL 2800.0 # AMZN 3050.0 # dtype: float64 # 创建 Series 并设置缺失值 data_with_na = [120.0, None, 1800.0] stocks_with_na = ['MSFT', 'FB', 'NVDA'] series_with_na = pd.Series(data_with_na, index=stocks_with_na) # 检查缺失值 print(series_with_na.isna()) # MSFT False # FB True # NVDA False # dtype: bool # 填充缺失值 series_with_na.fillna(0) # MSFT 120.0 # FB 0.0 # NVDA 1800.0 # dtype: float64
- 示例:
2. DataFrame创建(二维数据帧)
- 基本语法:
pd.DataFrame(x, index=idx, columns=col)
,x为二维列表/数组/字典(值=1D列表/数组/Series)或其他DataFrame。index/columns默认range。 - 用列表/数组创建:
- 示例:
lst = [[1, 2, 3], [4, 5, 6]]
,df1 = pd.DataFrame(lst)
,默认index/columns=0-1/0-2。 - 分析:简单但标签无语义,适合快速原型。强调形状兼容(x.shape[0]=行,[1]=列)。
- 示例:
- 用字典创建:
- 示例:
data = {'行业': ['电商',...], '价格': [176.92,...], ...}
,df2 = pd.DataFrame(data, index=['BABA', 'JD',...])
,添加name=‘美股’,index.name=‘代号’,columns.name=‘特征’。 - 属性:
df2.values
返回object数组(混类型),df2.columns
/df2.index
返回带name的Index。 - 分析:字典键→columns,值→列数据,行标签需额外指定。示例为“美股”数据(BABA等,含行业/价格/交易量/雇员),异质(object/float64/int64)。values为object dtype,反映Pandas的灵活性。
- 示例:
- 查看函数:
df2.head()
/tail(3)
:首/尾行(默认5)。df2.describe()
:数值列统计(count/mean/std/min/25%/50%/75%/max),检查缺失/异常。df2.info()
:详尽信息(类/行/列/非空/dtype/内存)。- 分析:这些函数是EDA起点,describe忽略object列,info显示内存使用(240.0+ bytes)。教学点:先用info检查数据完整性。
- 升维(MultiIndex):
- 示例:
df2.index = pd.MultiIndex.from_tuples([('中国公司','BABA'), ...])
,添加多层行标签(中国/美国公司)。 - 分析:MultiIndex.from_tuples用元组列表创建,扩展维度(模拟Panel)。示例分组公司国籍,便于分层分析。陷阱:多层标签复杂化索引,需后续学习loc/iloc。
- 示例:
import pandas as pd
# 创建一个 DataFrame,包含股票数据
data = {
'Symbol': ['TSLA', 'GOOGL', 'MSFT', 'AMZN'],
'Price': [250.75, 2750.00, 300.50, 3300.25],
'Volume': [1500000, 800000, 1200000, 900000]
}
df = pd.DataFrame(data)
# 显示 DataFrame
print(df) # Symbol Price Volume
# 0 TSLA 250.75 1500000
# 1 GOOGL 2750.00 800000
# 2 MSFT 300.50 1200000
# 3 AMZN 3300.25 900000
# 访问特定列
df['Price'] # 0 250.75
# 1 2750.00
# 2 300.50
# 3 3300.25
# Name: Price, dtype: float64
# 访问多列
df[['Symbol', 'Volume']] # Symbol Volume
# 0 TSLA 1500000
# 1 GOOGL 800000
# 2 MSFT 1200000
# 3 AMZN 900000
# 使用 loc 按标签访问行
df.loc[1:2, ['Symbol', 'Price']] # Symbol Price
# 1 GOOGL 2750.00
# 2 MSFT 300.50
# 使用 iloc 按位置访问行
df.iloc[0:2, 0:2] # Symbol Price
# 0 TSLA 250.75
# 1 GOOGL 2750.00
# 添加新列 (价格变化百分比)
df['Price_Change'] = df['Price'] * 0.05
print(df) # Symbol Price Volume Price_Change
# 0 TSLA 250.75 1500000 12.5375
# 1 GOOGL 2750.00 800000 137.5000
# 2 MSFT 300.50 1200000 15.0250
# 3 AMZN 3300.25 900000 165.0125
(三)数据保存&加载
在数据分析流程中,为避免重复生成序列或数据帧(如DataFrame),通常会将生成的中间结果保存,后续直接加载复用。这一过程包含两个核心操作:保存(save)(为下次加载而存储数据)和加载(load)(从之前存储的位置读取数据)。
以常用的数据帧(DataFrame,如变量df)为例,支持多种通用存储格式(如Excel、CSV、SQL、HDF5),其保存操作通过通用语法 df.to_format() 实现,具体包括 df.to_excel()、df.to_csv()、df.to_sql()、df.to_hdf();
对应的加载操作则通过 pd.read_format() 实现(如 pd.read_excel()、pd.read_csv() 等),核心目的是通过存载机制减少重复计算,提升分析效率。
存载操作表
操作类型 | 核心功能 | 常用方法/语法(DataFrame为例) | 典型格式 |
---|---|---|---|
保存(Save) | 将数据帧存储为文件,供后续复用 | df.to_format() (如 to_excel() 、to_csv() 、to_sql() 、to_hdf() ) |
Excel / CSV / SQL / HDF5 |
加载(Load) | 从存储文件中读取数据,恢复为数据帧 | pd.read_format() (如 read_excel() 、read_csv() 、read_sql() 、read_hdf() ) |
Excel / CSV / SQL / HDF5 |
import pandas as pd
# 创建一个包含中国股市数据的 DataFrame
data = {
'Code': ['600519.SH', '000001.SZ', '601318.SH', '600036.SH'],
'Open_Price': [15.80, 12.45, 25.30, 30.10],
'Volume': [12000000, 8500000, 9500000, 7000000]
}
df = pd.DataFrame(data)
# 保存 DataFrame 到 CSV 文件
df.to_csv('stock_data.csv', index=False)
# 文件 'stock_data.csv' 已生成,内容类似:
# Code,Open_Price,Volume
# 600519.SH,15.8,12000000
# 000001.SZ,12.45,8500000
# 601318.SH,25.3,9500000
# 600036.SH,30.1,7000000
# 从 CSV 文件加载数据
loaded_df = pd.read_csv('stock_data.csv')
print(loaded_df) # Code Open_Price Volume
# 0 600519.SH 15.8 12000000
# 1 000001.SZ 12.45 8500000
# 2 601318.SH 25.3 9500000
# 3 600036.SH 30.1 7000000
# 保存 DataFrame 到 Excel 文件
df.to_excel('stock_data.xlsx', index=False)
# 文件 'china_stock_data.xlsx' 已生成,内容与 CSV 类似
# 从 Excel 文件加载数据
loaded_excel = pd.read_excel('stock_data.xlsx')
print(loaded_excel) # Code Open_Price Volume
# 0 600519.SH 15.8 12000000
# 1 000001.SZ 12.45 8500000
# 2 601318.SH 25.3 9500000
# 3 600036.SH 30.1 7000000
# 保存到 JSON 格式
df.to_json('stock_data.json', orient='records')
# 文件 'china_stock_data.json' 已生成,内容类似:
# [{"Code":"600519.SH","Open_Price":15.8,"Volume":12000000}, ...]
# 从 JSON 文件加载数据
loaded_json = pd.read_json('stock_data.json')
print(loaded_json) # Code Open_Price Volume
# 0 600519.SH 15.8 12000000
# 1 000001.SZ 12.45 8500000
# 2 601318.SH 25.3 9500000
# 3 600036.SH 30.1 7000000
(四)数据获取
Pandas 中的 DataFrame(数据帧)的获取操作,涵盖元素、列、行、块及高级获取方式。本次内容从基础语法入手,强调其与 NumPy 数组的区别(Pandas 支持标签索引(label-based)和位置索引(position-based)),通过代码示例展开,每种获取方式均配有“场景”分类、代码输出及操作建议,突出 loc
/iloc
的清晰性与一致性。分析重点包括语法对比、潜在歧义(如 []
的多义性)、视图机制(获取操作返回子视图)及实际应用(如股票数据的筛选过滤)。
1. 元素获取(单元素)
单元素获取聚焦于精确提取 DataFrame 中的某个标量值,核心通过 标签索引(at) 和 位置索引(iat) 实现。
两种方式均直接返回标量结果,避免了通用索引(如 []
)优先按列检索的歧义问题。其中,at
基于行/列标签定位(如 df.at['index_i', 'attribute_j']
),iat
基于行/列数字位置定位(如 df.iat[i, j]
)。此方法的优势在于清晰性与一致性:无多义性风险,且执行效率高,特别适用于需要直接更新单个值(如修改特定股票价格)的场景。
潜在陷阱需注意:若标签不存在会触发 KeyError
,位置索引越界则报 IndexError
,需提前校验索引有效性。
import pandas as pd
# 创建一个简单的 DataFrame
data = {
'行业': ['科技', '金融', '零售', '制造'],
'价格': [150.5, 2800.0, 300.5, 1800.0],
'交易量': [1200000, 800000, 1000000, 900000]
}
df = pd.DataFrame(data, index=['公司A', '公司B', '公司C', '公司D'])
# 使用 at 基于标签获取元素
df.at['公司B', '价格'] # 2800.0
# 使用 iat 基于位置获取元素
df.iat[2, 1] # 300.5
# 尝试使用 [] 获取元素 (可能歧义)
df['价格'][1] # 2800.0 (但 [] 优先列访问,需注意)
# 修改元素值使用 at
df.at['公司C', '交易量'] = 950000
print(df.loc['公司C', '交易量']) # 950000
2. 列状获取
列状获取针对 DataFrame 的垂直方向数据(列),分为单列与多列两类操作。
单列提取支持四种方式(如属性式 df.列名
、标签式 df['列名']
、全行标签定位 df.loc[:, '列名']
、全行位置定位 df.iloc[:, i]
),核心差异在于标签是否允许空格(如 df.列名
要求列名无空格)、语法灵活性(如 df['列名']
更通用但易与列操作混淆)及一致性(loc/iloc
定位明确且跨维度统一)。
多列提取则通过列表传参(如 df[['列1', '列2']]
)、标签切片(如 df.loc[:, '列1':'列2']
,闭区间含端点)或位置切片(如 df.iloc[:, i:j]
,前闭后开)实现,返回子 DataFrame 视图。此类操作的关键在于视图机制——修改提取的列会影响原 DataFrame,且需注意标签切片的闭区间特性与位置切片的前闭后开差异。推荐使用 loc/iloc
避免 []
的多义性,尤其适用于特征选择(如机器学习变量提取)。
import pandas as pd
# 创建一个包含中国股市数据的 DataFrame
data = {
'代码': ['600519', '000001', '601318', '300059'],
'价格': [15.80, 12.45, 25.30, 18.75],
'交易量': [12000000, 8500000, 9500000, 11000000],
'类型': ['白酒', '银行', '保险', '科技']
}
df = pd.DataFrame(data, index=['贵州茅台', '平安银行', '中国平安', '东方财富'])
# 属性方式访问单列 (标签无空格)
df.价格 # 贵州茅台 15.80
# 平安银行 12.45
# 中国平安 25.30
# 东方财富 18.75
# Name: 价格, dtype: float64
# 中括号方式访问单列
df['交易量'] # 贵州茅台 12000000
# 平安银行 8500000
# 中国平安 9500000
# 东方财富 11000000
# Name: 交易量, dtype: int64
# loc 方式访问单列
df.loc[:, '类型'] # 贵州茅台 白酒
# 平安银行 银行
# 中国平安 保险
# 东方财富 科技
# Name: 类型, dtype: object
# iloc 方式访问单列
df.iloc[:, 2] # 贵州茅台 12000000
# 平安银行 8500000
# 中国平安 9500000
# 东方财富 11000000
# Name: 交易量, dtype: int64
# 列表方式访问多列
df[['代码', '价格']] # 代码 价格
# 贵州茅台 600519 15.80
# 平安银行 000001 12.45
# 中国平安 601318 25.30
# 东方财富 300059 18.75
# loc 切片访问多列
df.loc[:, '价格':'类型'] # 价格 交易量 类型
# 贵州茅台 15.80 12000000 白酒
# 平安银行 12.45 8500000 银行
# 中国平安 25.30 9500000 保险
# 东方财富 18.75 11000000 科技
# iloc 切片访问多列
df.iloc[:, 1:3] # 价格 交易量
# 贵州茅台 15.80 12000000
# 平安银行 12.45 8500000
# 中国平安 25.30 9500000
# 东方财富 18.75 11000000
3. 行状获取
行状获取聚焦于 DataFrame 的水平方向数据(行),分为单行与多行两类操作。
单行提取可通过标签定位(如 df.loc['行标签', :]
返回 Series 降维)、位置定位(如 df.iloc[i, :]
返回 Series)、标签切片(如 df['行标签':'行标签']
返回单行 DataFrame 保持维度)或位置切片(如 df[i:i+1]
返回单行 DataFrame 保持维度)实现,其中 :
表示选取所有列。
多行提取则通过标签区间(如 df.loc['起始行':'结束行', :]
)、位置区间(如 df.iloc[i:j, :]
)、标签切片(如 df['起始行':'结束行']
)或位置切片(如 df[i:j]
)完成,同样返回子 DataFrame 视图。行状操作的核心差异在于:loc/iloc
返回 Series(单行降维)或子 DataFrame(多行),而 []
切片可能因优先级问题误判为列操作(如 df['行标签']
优先查列)。
此外,标签切片为闭区间(含两端)、位置切片为前闭后开(不含右端点),且需注意 i:i+1
的维度保持技巧。推荐优先使用 loc/iloc
以保证语法一致性与操作清晰性,尤其适用于时间序列等连续行数据的提取。
import pandas as pd
# 创建一个包含中国股市数据的 DataFrame
data = {
'代码': ['600519', '000001', '601318', '300059'],
'价格': [15.80, 12.45, 25.30, 18.75],
'交易量': [12000000, 8500000, 9500000, 11000000],
'类型': ['白酒', '银行', '保险', '科技']
}
df = pd.DataFrame(data, index=['贵州茅台', '平安银行', '中国平安', '东方财富'])
# 使用 loc 访问单行 (基于标签,返回 Series)
df.loc['平安银行', :] # 代码 000001
# 价格 12.45
# 交易量 8500000
# 类型 银行
# Name: 平安银行, dtype: object
# 使用 iloc 访问单行 (基于位置,返回 Series)
df.iloc[2, :] # 代码 601318
# 价格 25.3
# 交易量 9500000
# 类型 保险
# Name: 中国平安, dtype: object
# 使用 [] 访问单行 (标签切片,返回 DataFrame)
df['中国平安':'中国平安'] # 代码 价格 交易量 类型
# 中国平安 601318 25.3 9500000 保险
# 使用 [] 访问单行 (位置切片,返回 DataFrame)
df[1:2] # 代码 价格 交易量 类型
# 平安银行 000001 12.45 8500000 银行
# 使用 loc 访问多行 (基于标签,返回 DataFrame)
df.loc['平安银行':'东方财富', :] # 代码 价格 交易量 类型
# 平安银行 000001 12.45 8500000 银行
# 中国平安 601318 25.30 9500000 保险
# 东方财富 300059 18.75 11000000 科技
# 使用 iloc 访问多行 (基于位置,返回 DataFrame)
df.iloc[0:3, :] # 代码 价格 交易量 类型
# 贵州茅台 600519 15.80 12000000 白酒
# 平安银行 000001 12.45 8500000 银行
# 中国平安 601318 25.30 9500000 保险
# 使用 [] 访问多行 (标签切片,返回 DataFrame)
df['贵州茅台':'中国平安'] # 代码 价格 交易量 类型
# 贵州茅台 600519 15.80 12000000 白酒
# 平安银行 000001 12.45 8500000 银行
# 中国平安 601318 25.30 9500000 保险
# 使用 [] 访问多行 (位置切片,返回 DataFrame)
df[1:4] # 代码 价格 交易量 类型
# 平安银行 000001 12.45 8500000 银行
# 中国平安 601318 25.30 9500000 保险
# 东方财富 300059 18.75 11000000 科技
4. 块状获取(返回子DataFrame)
块状获取针对 DataFrame 的二维连续区域(行与列的组合),仅支持通过标签切片(如 df.loc['起始行':'结束行', '起始列':'结束列']
,标签闭区间)或位置切片(如 df.iloc[i:j, k:l]
,位置前闭后开)两种方式,返回子 DataFrame 视图。
此类操作的本质是行列切片的组合应用,无需依赖通用索引(如 []
),从而规避了多义性风险。其核心特点是直接提取二维连续块(如交叉特征矩阵),适用于需要同时分析多行多列数据的场景(如多变量关联分析)。
需强调标签切片的闭区间特性(含两端)与位置切片的前闭后开差异(不含右端点),并指出块状获取是唯一无需 []
即可实现二维连续提取的方式,进一步强化了 loc/iloc
在高维数据操作中的通用性与可靠性。
import pandas as pd
# 创建一个包含中国股市数据的 DataFrame
data = {
'代码': ['600519', '000001', '601318', '300059'],
'价格': [15.80, 12.45, 25.30, 18.75],
'交易量': [12000000, 8500000, 9500000, 11000000],
'类型': ['白酒', '银行', '保险', '科技']
}
df = pd.DataFrame(data, index=['贵州茅台', '平安银行', '中国平安', '东方财富'])
# 使用 loc 获取块状数据 (基于标签)
df.loc['平安银行':'中国平安', '价格':'交易量'] # 价格 交易量 类型
# 平安银行 12.45 8500000 银行
# 中国平安 25.30 9500000 保险
# 使用 iloc 获取块状数据 (基于位置)
df.iloc[1:3, 1:3] # 价格 交易量
# 平安银行 12.45 8500000
# 中国平安 25.30 9500000
# 使用 loc 获取特定行和列 (混合标签)
df.loc[['贵州茅台', '东方财富'], ['代码', '类型']] # 代码 类型
# 贵州茅台 600519 白酒
# 东方财富 300059 科技
# 使用 iloc 获取特定行和列 (混合位置)
df.iloc[[0, 3], [0, 3]] # 代码 类型
# 贵州茅台 600519 白酒
# 东方财富 300059 科技
# 使用 loc 结合布尔条件获取块状数据
df.loc[df['价格'] > 15, '代码':'交易量'] # 代码 价格 交易量 类型
# 贵州茅台 600519 15.80 12000000 白酒
# 中国平安 601318 25.30 9500000 保险
# 东方财富 300059 18.75 11000000 科技
# 使用 iloc 结合切片获取块状数据
df.iloc[0:2, 0:2] # 代码 价格
# 贵州茅台 600519 15.80
# 平安银行 000001 12.45
5. 高级获取
高级获取通过布尔索引(如 df[条件表达式]
)或可调用对象(如 df.loc[:, lambda df: 列筛选函数]
)实现动态数据筛选,通常与 loc/iloc/[]
结合使用。
布尔索引常见于行级筛选(如 df.loc[df['价格'] > 100, :]
提取高价股),通过生成布尔 Series 过滤满足条件的行;可调用对象则更灵活(如 df.loc[:, lambda df: df.columns.str.contains('价')]
匹配列名含“价”的列),适用于列级条件筛选(但较少见,因列名通常固定且数量有限)。
此类操作的核心价值在于动态生成目标索引,返回视图并支持链式操作(如连续筛选与计算)。潜在陷阱包括布尔列筛选的罕见性(因列名固定时直接指定更高效)、以及条件表达式的逻辑复杂性(需确保条件生成正确)。
该操作适用于需按条件动态提取数据的场景,例如异常值检测、特征工程预处理等。
import pandas as pd
# 创建一个包含中国股市数据的 DataFrame
data = {
'代码': ['600519', '000001', '601318', '300059'],
'价格': [15.80, 12.45, 25.30, 18.75],
'交易量': [12000000, 8500000, 9500000, 11000000],
'类型': ['白酒', '银行', '保险', '科技']
}
df = pd.DataFrame(data, index=['贵州茅台', '平安银行', '中国平安', '东方财富'])
# 使用布尔索引筛选价格大于 20 的行 (常见用法)
df[df['价格'] > 20] # 代码 价格 交易量 类型
# 中国平安 601318 25.30 9500000 保险
# 使用布尔索引筛选交易量小于 10000000 的行 (常见用法)
df[df['交易量'] < 10000000] # 代码 价格 交易量 类型
# 平安银行 000001 12.45 8500000 银行
# 中国平安 601318 25.30 9500000 保险
# 使用 callable (lambda) 筛选列名包含 '价' 的列 (罕见用法)
df.loc[:, lambda df: df.columns.str.contains('价')] # 价格
# 贵州茅台 15.80
# 平安银行 12.45
# 中国平安 25.30
# 东方财富 18.75
# 使用 callable 筛选行标签包含 '平' 的行 (罕见用法)
df.loc[lambda df: df.index.str.contains('平'), :] # 代码 价格 交易量 类型
# 平安银行 000001 12.45 8500000 银行
# 中国平安 601318 25.30 9500000 保险
# 组合布尔索引与 callable
df[df['交易量'] > 9000000].loc[:, lambda df: ['代码', '类型']] # 代码 类型
# 贵州茅台 600519 白酒
# 中国平安 601318 保险
# 东方财富 300059 科技
6. 高级获取
层级获取主要针对含多层索引的序列与数据帧,核心是基于多层索引的层级关系提取数据,具体内容可分为“层级获取序列”和“层级获取数据帧”两部分:
(1)层级获取序列
对于含多层行索引的序列(如“日期+股票代码”双层索引),其索引以“元组”形式存储(每个元组包含各层级标签),获取逻辑围绕“按层级标签筛选”设计。基础操作包括:通过 []
或 loc
指定第一层索引标签(如日期)获取该层级下的全部数据;通过 loc[层级0标签, 层级1标签]
精准定位两层标签交叉的数据。
跨层级筛选则通过固定某一层标签(如第二层“股票代码”)并遍历其他层(如所有日期),例如 loc[:, 'GS']
可获取所有日期中“GS”对应的序列数据。
此类操作的核心是依托元组结构的层级标签,通过单层或多层组合筛选实现数据定位。
(2)层级获取数据帧
对于行索引和列索引均为多层的数据帧(如行索引“地区+代号”、列索引“数据类型+细分特征”),获取逻辑需同时考虑行层级与列层级的组合关系。
行层级获取分为单层(如通过 loc[第一层标签, :]
获取某一地区所有列数据)和多层组合(如 loc[(第一层标签, 第二层标签), :]
定位特定地区与代号交叉的行数据);列层级获取同理,支持单层(如 loc[:, 第一层列标签]
获取某一数据类型所有行数据)和多层组合(如 loc[:, (第一层列标签, 第二层列标签)]
定位细分特征交叉的列数据)。
更复杂的行列组合筛选(如 loc[(行层级组合), (列层级组合)]
)可精准定位单个或多个数据点。此外,索引辅助操作(如 swaplevel
交换层级顺序、set_index
/reset_index
调整索引结构、sort_index
按层级排序)进一步增强了数据获取的灵活性与效率,适配不同分析场景的需求。
import pandas as pd
# 创建一个多级索引 DataFrame,使用中国股市数据
index = pd.MultiIndex.from_tuples([('上海', '600519'),
('深圳', '000001'), ('上海', '601318'), ('深圳', '300059')], names=['地区', '代码'])
data = {
'价格': [15.80, 12.45, 25.30, 18.75],
'交易量': [12000000, 8500000, 9500000, 11000000],
'类型': ['白酒', '银行', '保险', '科技']
}
df = pd.DataFrame(data, index=index)
# 显示 DataFrame
print(df) # 价格 交易量 类型
# 地区 代码
# 上海 600519 15.80 12000000 白酒
# 深圳 000001 12.45 8500000 银行
# 上海 601318 25.30 9500000 保险
# 深圳 300059 18.75 11000000 科技
# 层级获取:使用元组访问特定行
df.loc[('上海', '600519')] # 价格 15.8
# 交易量 12000000
# 类型 白酒
# Name: (上海, 600519), dtype: object
# 层级获取:访问特定地区的子 DataFrame
df.loc['深圳'] # 价格 交易量 类型
# 代码
# 000001 12.45 8500000 银行
# 300059 18.75 11000000 科技
# 层级获取:使用切片访问多行
df.loc[('上海', '600519'):('深圳', '000001')] # 价格 交易量 类型
# 地区 代码
# 上海 600519 15.80 12000000 白酒
# 深圳 000001 12.45 8500000 银行
# 层级获取:结合列访问块状数据
df.loc['上海', '价格':'类型'] # 价格 交易量 类型
# 代码
# 600519 15.80 12000000 白酒
# 601318 25.30 9500000 保险
(五)数据结合(data combination)
Pandas 中的数据结合(data combination)方法丰富多样,涵盖 合并(merge)、连接(join)、拼接(concat) 和 追加(append)。内容从基础操作入手,逐步扩展至多维度场景(如多列合并、多索引层级),重点强调各方法的 参数灵活性(如 how
、on
、join
等关键参数的用法)。
每个方法均配有代码示例,通过具体场景(如股票数据整合)突出其操作逻辑与实际应用价值。分析聚焦于 合并规则差异(如内连接/外连接的取舍)、多维度扩展能力(应对复杂数据结构)、性能对比(如 join
与 concat
的效率权衡),以及 潜在陷阱(如重复列名导致的冲突或数据冗余)。
1. 合并(Merge)
合并(pd.merge
)是 Pandas 中最核心的数据结合方法,通过列值匹配实现类似 SQL JOIN 的功能,支持多维度场景扩展。
其基本语法为 pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None)
,核心参数 how
(控制连接类型:内连接/外连接/左连接/右连接)与 on
/left_on
/right_on
(指定匹配列)共同决定了关联逻辑。内连接(inner
)仅保留左右表中匹配列的共同值(交集),高效但数据量较少;外连接(outer
)保留所有值(并集),缺失部分填充 NaN,全面但可能冗余;左连接(left
)与右连接(right
)分别优先保留左表或右表的全部索引,另一表的缺失值补 NaN。
多列合并时需所有指定列的值严格对齐(如同时匹配“代号”和“日期”),适合处理时间序列等需多条件关联的场景。关键注意事项包括:匹配列名不一致时需用 left_on
/right_on
,但列值类型必须相同;多列合并可能导致重复列名(如“价格x”“价格y”),需后续重命名;连接类型的选择直接影响结果的数据量与完整性,需根据分析目标权衡。
2. 连接(Join)
连接(df1.join(df2, how='left')
)是专为索引设计的合并方法,本质是 merge
的索引简化版——默认基于行索引(而非列值)进行关联,参数 how
同样控制连接类型(内/外/左/右),但无需指定匹配列(直接以索引为关联键)。
单索引连接要求索引唯一(否则需先重置索引),多索引连接则支持多层索引(如按国家/股票分组的数据),扩展了维度灵活性。
相比 merge
,join
因少参数(无需手动指定列)、索引预排序特性,在索引对齐场景(如时间序列数据)中性能更优;但 merge
支持多列复杂条件匹配,灵活性更高。教学核心在于根据数据结构选择方法:若关联键为索引,优先用 join
;若需按列值匹配多条件,则用 merge
。潜在陷阱包括索引层级不匹配(多索引时需严格对齐)或索引非唯一(需预处理),均可能导致报错。
3. 拼接(Concat)
拼接(pd.concat
)通过沿指定轴(行或列)堆叠多个 DataFrame 实现数据结合,语法为 pd.concat([df1, df2], axis=0/1, join='outer/inner', ignore_index=False)
,核心参数 axis
(0 为行、1 为列)决定堆叠方向,join
控制交集(inner
)或并集(outer
)保留规则,ignore_index
可重置索引避免重复。
行拼接(axis=0
)适合追加数据(如合并多日股票记录),通过堆叠行并填充 NaN 处理非对齐索引;
列拼接(axis=1
)适合特征扩展(如新增雇员信息列),但需注意 join='inner'
会丢弃非交集索引行(仅保留共有的索引)。
相比 merge
,concat
逻辑更简单(无需匹配列),但灵活性较低(仅支持轴堆叠);ignore_index
参数是处理索引重复的关键工具。教学重点在于根据需求选择堆叠方向(行/列)与连接规则(交集/并集),并利用 ignore_index
维护索引唯一性。
4. 追加(Append)
追加(df1.append(df2)
)是 concat(axis=0)
的简化版,专门用于沿行轴堆叠 DataFrame,语法更直观但性能稍逊(底层仍复制数据)。该方法在早期 Pandas 版本中常用,但在 2.0 版本后已被官方标记为废弃,推荐使用 concat
替代。其核心逻辑与行拼接一致(堆叠行、填充 NaN),适合历史代码兼容场景,但新项目应优先选择功能更全面的 concat
。
import pandas as pd
# 创建第一个 DataFrame,包含部分股票数据
df1 = pd.DataFrame({
'代码': ['600519', '000001', '601318'],
'价格': [15.80, 12.45, 25.30],
'类型': ['白酒', '银行', '保险']
}, index=['贵州茅台', '平安银行', '中国平安'])
# 创建第二个 DataFrame,包含部分重叠股票数据
df2 = pd.DataFrame({
'代码': ['601318', '300059', '000001'],
'交易量': [9500000, 11000000, 8500000],
'市值': [180000, 120000, 150000]
}, index=['中国平安', '东方财富', '平安银行'])
# 合并 (merge) 使用内连接
pd.merge(df1, df2, on='代码', how='inner') # 代码 价格 类型 交易量 市值
# 0 000001 12.45 银行 8500000 150000
# 1 601318 25.30 保险 9500000 180000
# 合并 (merge) 使用外连接
pd.merge(df1, df2, on='代码', how='outer') # 代码 价格 类型 交易量 市值
# 0 000001 12.45 银行 8500000 150000
# 1 300059 NaN NaN 11000000 120000
# 2 600519 15.80 白酒 NaN NaN
# 3 601318 25.30 保险 9500000 180000
# 连接 (join) 使用左连接
df1.join(df2, how='left') # 代码 价格 类型 交易量 市值
# 贵州茅台 600519 15.80 白酒 NaN NaN
# 平安银行 000001 12.45 银行 8500000 150000
# 中国平安 601318 25.30 保险 9500000 180000
# 拼接 (concat) 沿行轴 (axis=0)
pd.concat([df1, df2], axis=0, ignore_index=True) # 代码 价格 类型 交易量 市值
# 0 600519 15.80 白酒 NaN NaN
# 1 000001 12.45 银行 NaN NaN
# 2 601318 25.30 保险 NaN NaN
# 3 601318 NaN NaN 9500000 180000
# 4 300059 NaN NaN 11000000 120000
# 5 000001 NaN NaN 8500000 150000
# 拼接 (concat) 沿列轴 (axis=1)
pd.concat([df1, df2], axis=1) # 代码 价格 类型 代码 价格 类型 交易量 市值
# 中国平安 NaN NaN NaN 601318 NaN NaN 9500000 180000
# 东方财富 NaN NaN NaN 300059 NaN NaN 11000000 120000
# 贵州茅台 600519 15.80 白酒 NaN NaN NaN NaN NaN
# 平安银行 000001 12.45 银行 000001 NaN NaN 8500000 150000
# 追加 (append) 两个 DataFrame
df1.append(df2) # 代码 价格 类型 交易量 市值
# 贵州茅台 600519 15.80 白酒 NaN NaN
# 平安银行 000001 12.45 银行 NaN NaN
# 中国平安 601318 25.30 保险 NaN NaN
# 中国平安 601318 NaN NaN 9500000 180000
# 东方财富 300059 NaN NaN 11000000 120000
# 平安银行 000001 NaN NaN 8500000 150000
(六)数据重塑(reshape)
Pandas 中的数据重塑(data reshaping)方法丰富多样,涵盖 透视表(pivot)、熔化(melt)、堆叠(stack)、去堆叠(unstack)、设置索引(set_index)、重置索引(reset_index) 以及 多级索引操作。
我将从基础操作入手,逐步扩展至多维度场景(如多列透视、多级索引嵌套),重点强调重塑操作的 灵活性 与 语义化表达(即通过变换让数据结构更贴合分析需求)。
每个方法均配有代码示例,通过具体场景(如股票数据分析)突出其操作逻辑与实际应用价值。分析聚焦于 维度变换规则(如行列转换逻辑)、索引/列的动态调整机制(如层级索引的生成与拆分)、性能对比(如 stack
与 pivot
的效率差异),以及 潜在陷阱(如多级索引冲突导致的键值重复或数据丢失)。
1、行列互转:
- 使用 melt 函数将宽格式数据转换为长格式(unpivot),通过指定 ID 变量和值变量重塑数据。
- 使用 pivot 函数将长格式数据转换为宽格式(pivot),通过索引、列和值列重新组织数据。
- 示例代码展示如何将列转为行或行转为列,适用于数据透视表生成。
2、堆叠与取消堆叠:
- stack 方法将 DataFrame 的列压缩为多级索引的行,创建长格式数据。
- unstack 方法将多级索引的行展开为列,恢复宽格式数据。
- 强调堆叠和取消堆叠的逆操作特性,适合处理多层次数据。
3、透视表:
- pivot_table 函数创建多维汇总表,支持按行、列和值进行聚合(默认使用均值)。
- 可指定聚合函数(如 sum、count)以及多级索引,适用于复杂数据分析场景。
- 示例展示如何根据类别汇总数据,如按地区和产品统计销售额。
4、交叉表:
- crosstab 函数生成频率或统计表,用于分析两个或多个分类变量之间的关系。
- 支持添加权重列或自定义聚合函数,适用于统计分布和关联性分析。
- 示例可能包括按性别和职业分组的计数表。
import pandas as pd
# 创建一个初始 DataFrame,包含中国股市数据
data = {
'公司': ['贵州茅台', '平安银行', '中国平安', '东方财富'],
'2023年': [15.80, 12.45, 25.30, 18.75],
'2024年': [16.20, 12.80, 26.50, 19.30]
}
df = pd.DataFrame(data)
# 使用 melt 将宽格式转为长格式
pd.melt(df, id_vars=['公司'], value_vars=['2023年', '2024年'],
var_name='年份', value_name='价格') # 公司 年份 价格
# 0 贵州茅台 2023年 15.80
# 1 平安银行 2023年 12.45
# 2 中国平安 2023年 25.30
# 3 东方财富 2023年 18.75
# 4 贵州茅台 2024年 16.20
# 5 平安银行 2024年 12.80
# 6 中国平安 2024年 26.50
# 7 东方财富 2024年 19.30
# 使用 pivot 将长格式转为宽格式
long_df = pd.melt(df, id_vars=['公司'], value_vars=['2023年', '2024年'],
var_name='年份', value_name='价格')
long_df.pivot(index='公司', columns='年份', values='价格') # 年份 2023年 2024年
# 公司
# 东方财富 18.75 19.30
# 平安银行 12.45 12.80
# 贵州茅台 15.80 16.20
# 中国平安 25.30 26.50
# 使用 stack 将列转为多级索引
df.set_index('公司').stack() # 公司 2023年 15.80
# 东方财富 2024年 19.30
# 平安银行 2023年 12.45
# 2024年 12.80
# 贵州茅台 2023年 15.80
# 2024年 16.20
# 中国平安 2023年 25.30
# 2024年 26.50
# dtype: float64
# 使用 unstack 恢复多级索引为列
stacked = df.set_index('公司').stack()
stacked.unstack() # 年份 2023年 2024年
# 公司
# 东方财富 18.75 19.30
# 平安银行 12.45 12.80
# 贵州茅台 15.80 16.20
# 中国平安 25.30 26.50
# 使用 pivot_table 创建透视表,按公司和年份汇总平均价格 (修正为使用现有列)
pd.pivot_table(df, values='2023年', index='公司', columns='2024年', aggfunc='mean') # 2024年 12.80 16.20 19.30 26.50
# 公司
# 东方财富 NaN NaN 19.30 NaN
# 平安银行 12.80 NaN NaN NaN
# 贵州茅台 NaN 16.20 NaN NaN
# 中国平安 NaN NaN NaN 26.50
# 使用 crosstab 创建交叉表,按公司和类型统计频率
df['类型'] = ['白酒', '银行', '保险', '科技']
pd.crosstab(df['公司'], df['类型']) # 类型 白酒 银行 保险 科技
# 公司
# 东方财富 0 0 0 1
# 平安银行 0 1 0 0
# 贵州茅台 1 0 0 0
# 中国平安 0 0 1 0
(七) 数据分析
下面将介绍 Pandas 中数据分析的核心方法,涵盖统计描述(describe
)、分组聚合(groupby
)、时间序列分析(resample
)和滑动窗口分析(rolling
)。内容从基础统计入手,逐步扩展到复杂场景(如多级分组、时间频率转换),强调 Pandas 的分析灵活性和与 NumPy 的互操作性。
每种方法都会有举例说明操作的逻辑和应用场景(如股票数据统计)。分析重点在于统计指标的计算规则、分组轴的动态调整、时间序列的频率处理以及滑动窗口的性能优化。
1. 统计描述(Describe)
统计描述(df.describe
)提供数值列的统计摘要,是数据探索的常用工具。其基本语法为 df.describe(percentiles=None, include=None, exclude=None)
,通过参数 percentiles
自定义分位数(如 [0.1, 0.9]
),include/exclude
控制纳入统计的列类型(如仅数值列或包含非数值列)。默认输出包含计数(count
,含 NaN 的总行数)、均值(mean
)、标准差(std
,反映数据波动)、最小值(min
)、四分位数(25%
/50%
/75%
)和最大值(max
),仅针对数值列(如价格、交易量)进行分析,自动忽略非数值列(如行业)。多类型描述(include='all'
)可扩展至非数值列,额外输出众数(top
)及其频次(freq
),但需注意 NaN 会影响计数结果。教学核心在于通过快速统计摘要把握数据分布特征(如集中趋势、离散程度),为后续分析提供基础认知。
2. 分组聚合(Group By)
分组聚合(df.groupby
)通过指定分组键(如行业、代号)对数据进行分类统计,逻辑类似 SQL 的分组操作。基本语法为 df.groupby(by, axis=0, level=None).agg(func)
,其中 by
为分组依据(单键或多键),axis
指定分组方向(行或列),level
用于多级索引的层级选择,agg
支持多种聚合函数(如均值、求和)。
单键分组(如按“行业”聚合)沿行方向计算各组的统计量(如行业均价),自动排除非数值列;多键分组(如按“行业+代号”聚合)通过字典指定列与函数的映射关系(如价格取均值、交易量求和),生成多级索引的统计结果,适合复杂场景(如行业-股票联合分析)。
多级索引分组(通过 level
参数)可灵活处理层级数据(如按国家/代号聚合),但需确保层级结构匹配,否则报错。分析重点包括分组逻辑的灵活性(单键/多键/层级)、非数值列的自动排除规则,以及聚合函数对数据特征的提取能力。
3. 时间序列分析(Resample)
时间序列重采样(df.resample
)针对时间索引数据,通过指定频率规则(如日/月/年)对数据进行聚合,逻辑类似时间轴的分段统计。基本语法为 df.resample(rule, closed='left', label='left').agg(func)
,其中 rule
定义重采样频率(如 'D'
日、'M'
月、'Y'
年),closed
和 label
控制时间区间的开闭与标签归属。
基本重采样(如按月均值)将高频数据(如日数据)转换为低频统计(如月均股价),适合分析时间趋势;高级重采样支持多指标聚合(如价格均值与交易量求和),并通过参数调整时间区间的包含规则(如左闭区间)。
核心注意事项包括:时间索引必须为 Datetime 类型(非时间索引会报错),重采样频率需适配数据周期(如日数据可转为周/月/年),以及多指标聚合时需明确各列的统计函数。分析价值在于通过时间维度的灵活聚合,揭示数据的周期性、趋势性特征(如股价的季节性波动)。
4. 滑动窗口分析(Rolling)
滑动窗口分析(df.rolling
)通过定义窗口大小(如 3 天、5 周),计算窗口内的滑动统计量(如移动平均、累计求和),是时间序列局部特征提取的核心工具。基本语法为 df.rolling(window, min_periods=None).agg(func)
,其中 window
指定窗口包含的数据点数量,min_periods
控制窗口内最少有效数据点(允许部分缺失)。基本滑动(如 3 天移动平均)通过对连续窗口内的数据计算统计量(如均值),实现趋势平滑(如过滤股价短期波动);高级滑动支持多指标同时计算(如价格均值与交易量求和),并通过 min_periods
调整窗口的有效性阈值(如至少 3 个有效点才计算)。注意事项包括:窗口过大会导致数据丢失(边缘数据点不足时被剔除),大窗口计算可能影响性能(需测试优化),以及 min_periods
的设置需平衡数据完整性与统计有效性。分析重点在于通过局部统计捕捉时间序列的短期模式(如均线交叉、波动聚集),为短期决策提供依据。
import pandas as pd
# 创建一个包含中国股市数据的 DataFrame
data = {
'代码': ['600519', '000001', '601318', '300059'],
'价格': [15.80, 12.45, 25.30, 18.75],
'交易量': [12000000, 8500000, 9500000, 11000000],
'日期': ['2025-09-28', '2025-09-29', '2025-09-30', '2025-09-30']
}
df = pd.DataFrame(data)
df['日期'] = pd.to_datetime(df['日期'])
# 统计描述 (describe)
df.describe() # 价格 交易量
# count 4.000000 4.000000e+00
# mean 18.075000 9.750000e+06
# std 5.262477 1.707825e+06
# min 12.450000 8.500000e+06
# 25% 14.637500 8.875000e+06
# 50% 17.275000 9.750000e+06
# 75% 20.662500 1.075000e+07
# max 25.300000 1.200000e+07
# 分组聚合 (groupby) 按日期计算平均值
df.groupby('日期').mean() # 价格 交易量
# 日期
# 2025-09-28 15.80 1.200000e+07
# 2025-09-29 12.45 8.500000e+06
# 2025-09-30 22.025 1.025000e+07
# 时间序列分析 (resample) 按日统计交易量总和
df.set_index('日期').resample('D')['交易量'].sum() # 日期
# 2025-09-28 12000000
# 2025-09-29 8500000
# 2025-09-30 20500000
# Freq: D, Name: 交易量, dtype: int64
# 滑动窗口分析 (rolling) 计算 2 天滑动平均价格
df.set_index('日期')['价格'].rolling(window=2).mean() # 日期
# 2025-09-28 NaN
# 2025-09-29 14.125
# 2025-09-30 18.875
# Name: 价格, dtype: float64
(八)数据可视化
接下来我要介绍的是 Pandas 自带的数据可视化方法——也就是用 Series.plot()
或 DataFrame.plot()
直接画图。这里要先提一下,大家可能听说过两个超常用的画图工具包:Matplotlib 和 **Seaborn,**它们功能超强,能画出各种复杂又漂亮的图。
Pandas 自带的 plot()
方法(比如 df.plot()
或 series.plot()
)最大的特点是 “快” ——你只需要一行代码,就能把数据变成图表,快速看到数据之间的关系(比如哪列数据在涨、哪列数据有波动)。它画出来的图 “颜值”不会太高(可能不够美观),而且 信息量可能没那么全(比如细节不够丰富)。但它特别适合 “先看看数据大概长啥样” 的场景——比如你刚拿到一组数据,想快速检查下趋势、对比下数值,用它就特别方便。
图表类型均结合具体数据场景(如股票数据、业务指标数据)说明用法,重点覆盖 6 类基础图表:
- 线状图:主要用于展示数据随时间的变化趋势,如股票每日收盘价的连续波动、月度销售额的变化曲线,通过
df.plot(kind='line')
实现,支持标注坐标轴、添加图例以清晰呈现趋势。 - 散点图:用于分析两个变量间的关联关系,如股票成交量与价格的相关性、广告投入与销量的对应关系,通过
df.plot(kind='scatter')
生成,可通过颜色、大小区分不同类别数据。 - 条形图:适用于对比不同类别数据的数值差异,如不同行业股票的平均收益率、不同产品的月度销量,通过
df.plot(kind='bar')
实现,支持横向 / 纵向条形图切换,适配不同类别名称长度的展示需求。 - 箱形图:用于展示数据的分布特征(如四分位数、异常值),如某股票价格的波动范围、不同区域用户消费金额的分布差异,通过
df.plot(kind='box')
生成,可快速识别数据中的异常值(如远超正常范围的极端价格)。 - 直方图:用于呈现数据的频率分布,如股票日收益率的分布情况、用户年龄的分布区间,通过
df.plot(kind='hist')
实现,支持调整分组数量(bins)以优化分布细节的展示。 - 饼状图:用于展示各部分占总体的比例关系,如不同板块股票在投资组合中的权重、不同渠道收入占总营收的比例,通过
df.plot(kind='pie')
实现,支持标注百分比、调整起始角度以提升可读性。
import pandas as pd
import matplotlib.pyplot as plt
# 创建一个包含中国股市数据的 DataFrame
data = {
'公司': ['贵州茅台', '平安银行', '中国平安', '东方财富'],
'收盘价': [15.80, 12.45, 25.30, 18.75],
'成交量': [12000000, 8500000, 9500000, 11000000],
'日期': ['2025-09-28', '2025-09-29', '2025-09-30', '2025-09-30'],
'板块': ['白酒', '银行', '保险', '科技'],
'收益率': [0.02, -0.01, 0.03, 0.015]
}
df = pd.DataFrame(data)
df['日期'] = pd.to_datetime(df['日期'])
# 线状图:展示收盘价随日期的变化趋势
df.plot(x='日期', y='收盘价', kind='line', marker='o', legend=True)
plt.title('每日收盘价趋势')
plt.xlabel('日期')
plt.ylabel('收盘价 (元)')
plt.grid(True)
plt.show() # [生成线状图,显示各公司收盘价随日期波动,点标记,包含图例和网格]
# 散点图:分析成交量与收益率的相关性 (修正:手动映射颜色)
color_map = {'白酒': 'red', '银行': 'blue', '保险': 'green', '科技': 'purple'}
colors = [color_map[plate] for plate in df['板块']]
df.plot(x='成交量', y='收益率', kind='scatter', c=colors, s=100)
plt.title('成交量与收益率关系')
plt.xlabel('成交量')
plt.ylabel('收益率')
plt.show() # [生成散点图,成交量 vs 收益率,颜色手动映射板块,点大小 100]
# 条形图:对比不同公司平均收盘价
df.groupby('公司')['收盘价'].mean().plot(kind='bar', color='skyblue')
plt.title('公司平均收盘价对比')
plt.xlabel('公司')
plt.ylabel('平均收盘价 (元)')
plt.xticks(rotation=45)
plt.show() # [生成纵向条形图,显示各公司平均收盘价,标签倾斜 45 度]
# 箱形图:展示收盘价分布特征
df.boxplot(column='收盘价', by='板块', grid=False)
plt.title('各板块收盘价分布')
plt.suptitle('') # 移除默认标题
plt.xlabel('板块')
plt.ylabel('收盘价 (元)')
plt.show() # [生成箱形图,显示各板块收盘价分布,包括四分位和异常值]
# 直方图:呈现收益率的频率分布
df['收益率'].plot(kind='hist', bins=5, color='green', edgecolor='black')
plt.title('收益率频率分布')
plt.xlabel('收益率')
plt.ylabel('频率')
plt.show() # [生成直方图,显示收益率分布,5 个分组,绿色填充]
# 饼状图:展示各板块在总成交量中的比例
sector_volumes = df.groupby('板块')['成交量'].sum()
sector_volumes.plot(kind='pie', autopct='%1.1f%%', startangle=90)
plt.title('板块成交量占比')
plt.ylabel('') # 移除 y 轴标签
plt.show() # [生成饼状图,显示各板块成交量比例,标注百分比,从 90 度开始]
(九)数据处理
最后我们总结 Pandas 的核心内容,重点讲解数据处理的核心技术。需要注意的是,前面课程中使用的所有数据均已经过处理,呈现为“干净”状态;但在实际工作中,原始数据通常是“杂乱”的,因此数据预处理是分析的第一步。数据处理主要可分为两类:
- 数据清洗(data cleansing):处理缺失值、离群值(异常值);
- 数据转换(data transforming):包括编码、分组等操作。
核心模块一:数据清洗
数据清洗是处理数据的基础环节,核心是“剔除无效数据、修复不完整数据”,该部分重点介绍 4 类常见问题的解决方案,均结合 Pandas 实操方法,适配小白理解与应用:
- 缺失值处理:针对数据中“空值(NaN)”问题,提供两类核心策略——
- 「删除法」:通过
dropna()
函数删除含缺失值的行或列(如删除股票数据中“收盘价”为空的记录),支持通过参数指定“删除含任意缺失值的行”或“删除全为缺失值的行”,避免无效数据干扰分析; - 「填补法」:通过
fillna()
函数用合理值填补缺失值(如用股票价格的“均值”“中位数”填补空值,或用前一天价格“向前填充”),适配不同场景下的缺失值修复需求,减少数据丢失。
- 「删除法」:通过
- 重复值处理:针对数据中“完全重复或部分重复”的记录,通过
duplicated()
函数识别重复数据(返回布尔值标记是否重复),再用drop_duplicates()
函数删除重复项,例如清理股票数据中“同一时间、同一股票代码”的重复记录,保证数据唯一性。 - 异常值处理:针对数据中“超出合理范围”的极端值(如股票价格突然出现的异常高值/低值),提供两类常用方法——
- 「统计法」:结合描述性统计(如
describe()
查看四分位数),用“3σ 原则”或“箱形图法”识别异常值(如超出上下四分位数 1.5 倍四分位距的值); - 「过滤法」:通过布尔索引筛选出合理范围的数据(如
df[df['价格'] < 1000]
剔除股票价格超 1000 的异常值),或用clip()
函数将极端值限制在合理区间内。
- 「统计法」:结合描述性统计(如
- 格式与内容纠错:针对数据“格式不统一”(如日期格式混乱、文本大小写不一致)或“内容错误”(如股票代码多写字符)的问题,通过 Pandas 字符串方法(如
str.strip()
去除多余空格、str.upper()
统一文本大写)、日期转换函数(如pd.to_datetime()
规范日期格式)修正,确保数据格式符合分析要求。
核心模块二:数据转换
数据转换是为了“让数据结构/格式适配分析场景”,该部分重点介绍 3 类高频转换操作,均结合实际场景(如股票数据处理)说明用法:
- 数据类型转换:针对数据类型不匹配的问题(如“价格”字段误存为文本类型),通过
astype()
函数转换数据类型(如df['价格'].astype('float')
将文本转为数值型),或用pd.to_numeric()
处理含非数字字符的数值字段(如pd.to_numeric(df['成交量'], errors='coerce')
将无法转换的值设为 NaN),确保后续统计计算正常进行。 - 数据格式重塑:针对数据结构不便于分析的问题(如“宽表”转“长表”、行列互换),衔接前文“数据重塑”内容,重点应用
melt()
(将多列数据“熔化”为长表,如将多只股票的价格列转为“股票代码-价格”的两列结构)、pivot()
(将长表“透视”为宽表,如将“日期-股票代码-价格”的长表转为“日期为行、股票代码为列”的宽表),适配不同分析场景下的数据结构需求。 - 自定义转换:针对个性化分析需求(如股票收益率计算、数据分组映射),通过
apply()
或map()
函数实现自定义转换——apply()
:对行或列应用自定义函数(如df['收益率'].apply(lambda x: x*100)
将收益率转为百分比);map()
:对列数据进行映射转换(如将“行业代码”映射为“行业名称”,如df['行业代码'].map({1:'电商', 2:'科技'})
),让数据更贴合分析语义。
import pandas as pd
import numpy as np
# 创建一个包含中国股市数据的 DataFrame 模拟原始“杂乱”数据
data = {
'代码': ['600519', '000001', '601318', '300059', '600519'],
'价格': [15.80, '12.45', 25.30, np.nan, 15.80],
'交易量': ['12,000,000', '8,500,000', '9,500,000', '11,000,000', '12,000,000'],
'日期': ['2025-09-28', '29-09-2025', '2025/09/30', '30-09-2025', '2025-09-28'],
'行业': ['白酒', '银行 ', ' 保险', '科技', '白酒']
}
df = pd.DataFrame(data)
# 缺失值处理 - 删除法
df_cleaned_drop = df.dropna(subset=['价格']) # 删除价格为 NaN 的行
print(df_cleaned_drop) # 代码 价格 交易量 日期 行业
# 0 600519 15.80 12,000,000 2025-09-28 白酒
# 1 000001 12.45 8,500,000 29-09-2025 银行
# 2 601318 25.30 9,500,000 2025/09/30 保险
# 4 600519 15.80 12,000,000 2025-09-28 白酒
# 缺失值处理 - 填补法
df_filled = df.fillna({'价格': df['价格'].mean()}) # 用均值填补价格缺失值
print(df_filled['价格']) # 0 15.80
# 1 12.45
# 2 25.30
# 3 17.4625
# 4 15.80
# Name: 价格, dtype: object
# 重复值处理
df_no_duplicates = df.drop_duplicates(subset=['代码', '日期'])
print(df_no_duplicates) # 代码 价格 交易量 日期 行业
# 0 600519 15.80 12,000,000 2025-09-28 白酒
# 1 000001 12.45 8,500,000 29-09-2025 银行
# 2 601318 25.30 9,500,000 2025/09/30 保险
# 3 300059 NaN 11,000,000 30-09-2025 科技
# 异常值处理 - 过滤法
df_filtered = df[df['价格'].astype(float) < 20] # 筛选价格小于 20 的数据
print(df_filtered) # 代码 价格 交易量 日期 行业
# 0 600519 15.80 12,000,000 2025-09-28 白酒
# 1 000001 12.45 8,500,000 29-09-2025 银行
# 4 600519 15.80 12,000,000 2025-09-28 白酒
# 格式与内容纠错 - 日期格式与文本清洗
df['日期'] = pd.to_datetime(df['日期'], errors='coerce')
df['行业'] = df['行业'].str.strip().str.lower()
print(df['日期']) # 0 2025-09-28
# 1 2025-09-29
# 2 2025-09-30
# 3 2025-09-30
# 4 2025-09-28
# Name: 日期, dtype: datetime64[ns]
print(df['行业']) # 0 白酒
# 1 银行
# 2 保险
# 3 科技
# 4 白酒
# Name: 行业, dtype: object
# 数据类型转换
df['价格'] = pd.to_numeric(df['价格'], errors='coerce')
df['交易量'] = df['交易量'].str.replace(',', '').astype(int)
print(df.dtypes) # 代码 object
# 价格 float64
# 交易量 int64
# 日期 datetime64[ns]
# 行业 object
# dtype: object
# 数据格式重塑 - melt
df_melted = pd.melt(df, id_vars=['代码', '日期'], value_vars=['价格', '交易量'],
var_name='指标', value_name='值')
print(df_melted.head()) # 代码 日期 指标 值
# 0 600519 2025-09-28 价格 15.80
# 1 000001 2025-09-29 价格 12.45
# 2 601318 2025-09-30 价格 25.30
# 3 300059 2025-09-30 价格 NaN
# 4 600519 2025-09-28 价格 15.80
# 自定义转换 - apply
df['收益率'] = df['价格'].apply(lambda x: x * 0.01 if pd.notna(x) else x)
print(df['收益率']) # 0 0.1580
# 1 0.1245
# 2 0.2530
# 3 NaN
# 4 0.1580
# Name: 收益率, dtype: float64
# 自定义转换 - map
df['行业类别'] = df['行业'].map({'白酒': '消费', '银行': '金融', '保险': '金融', '科技': '科技'})
print(df['行业类别']) # 0 消费
# 1 金融
# 2 金融
# 3 科技
# 4 消费
# Name: 行业类别, dtype: object
我们简单总结上述 Pandas 相关内容。Pandas 工具包主要用于数组操作,前文系统介绍了其八大核心应用方向:首先涵盖基础数据结构创建(构建一维序列和二维数据帧),随后涉及数据存载(支持 xlsx 和 csv 格式的序列/数据帧存储与加载);接着深入探讨数据获取(通过元素、列状、行状、块状、高级及层级六种方式索引或切片数据帧元素),以及数据整合(基于键的合并和按轴的连接操作);然后聚焦数据形态变换(通过行列互转重塑索引以及长宽互转调整表达形式),并进行数据分析(包括整体、分组、透视和交叉四类分析方法);还包括数据可视化(以线状图、散点图、条形图、箱形图、直方图和饼状图等多种形式呈现);最后讲解数据预处理(清洗缺失值和离群值,以及对字符型变量编码和连续型变量分组转换),全面覆盖从数据构建到分析的全流程操作。
总结
我们已经介绍了 NumPy 和 Pandas 工具库,它们是数据分析时最基础的两个工具库。没有 NumPy,量化分析的计算效率会大幅下降;没有 Pandas,数据处理与业务逻辑实现会变得极其繁琐。二者共同构成了量化研究的“基础设施”——就像“计算器 + Excel”的升级版,但功能更强大、效率更高。对于任何严肃的量化从业者(无论是主观策略开发者还是算法交易工程师),熟练掌握这两个库是入门门槛,更是长期竞争力所在。
下次课程会继续介绍 Python 的进阶内容,包括 Python 的一些高级用法和其他工具库。