文科生の量化闯关记 : 代码篇- Python进阶1
  我是宽客 3天前 40 0

一、引言

Python 是一种“胶水语言”,能够整合多种库与工具!

image.png

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记录时间。

image.png

从上述结果可以发现,数组的计算效率约为列表的 14 倍。需要注意的是,每次运行的具体时间可能不完全相同,但两者的效率差距始终保持在 10~15 倍 左右。

由此可见,若元素全部为数值型变量,数组是一种非常高效的数据结构。因此,我们非常有必要学习数组的使用。

(二)创建数值型和结构数组

1、创建 NumPy 数组

创建 NumPy 数组有三种方式:

  1. 基础构造法:将函数 np.array()用在列表和元组上。
  2. 规则生成法:用函数 np.arange() 和 np.linspace() 创建。
  3. 快速初始化法:用函数 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:元素类型(如float64int32)。
  • 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 三种格式为例详细说明:

  1. 自身 .npy 格式
  • 保存:使用 np.save(npy_file, arr) 函数,将NumPy数组保存为 .npy 格式文件。
  • 加载:使用 np.load(npy_file) 函数,加载已保存的 .npy 格式数组。
  • 优势:适合NumPy内部使用,加载快速,无需额外参数。
  1. 文本 .txt 格式
  • 保存:通过 np.savetxt(txt_file, arr) 函数,把数组保存为 .txt 文本格式。
  • 加载:利用 np.loadtxt(txt_file) 函数,加载 .txt 格式的数组。
  • 注意:默认使用空格分隔,适合简单数值数据。
  1. 文本 .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()

image.png

  • 二维数组
    • 创建示例: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多列排序。适用于数据排名/分组。
  • 增减
    • 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 数组的升级版。

image.png

  • NumPy 数组:只存纯数字,没有“这一列是啥意思”“这一行是谁”的说明(比如一个二维数组,你只知道是一堆数,但不知道哪列是股价、哪行是日期)。
  • Pandas 数据结构:在 NumPy 数组的基础上,加了“描述信息”(比如行名、列名),让数据更易懂(比如表格里直接标着“平安银行”“茅台”,列名写着“2024-1-3 到 2025-1-3 的股价”)。

具体对应关系:

  • 一维数据(序列/Series) = 一维数组(比如一列数) + 行索引(比如这列数是哪个股票的价格)
  • 二维数据(数据帧/DataFrame) = 二维数组(比如多行多列的表格) + 行索引(比如每行是哪一天) + 列索引(比如每列是哪个股票)
  • 三维数据(面板/Panel) = 三维数组(比如多个二维表格叠一起) + 行/列/项索引(但 Pandas 1.0 后不用了,用多层索引的 DataFrame 代替)

重点区别:Pandas 是为**表格类杂数据(比如股价、姓名、日期混在一起)设计的,NumPy 是为纯数字的同类型数据(比如全是整数或小数)**设计的(除非用 NumPy 的特殊结构数组)。

image.png

(二)创建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更完整,类似于数据列的标题。
  • 用列表创建

    • 示例: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 + 3np.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)。内容从基础操作入手,逐步扩展至多维度场景(如多列合并、多索引层级),重点强调各方法的 参数灵活性(如 howonjoin等关键参数的用法)。

每个方法均配有代码示例,通过具体场景(如股票数据整合)突出其操作逻辑与实际应用价值。分析聚焦于 合并规则差异(如内连接/外连接的取舍)、多维度扩展能力(应对复杂数据结构)、性能对比(如 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同样控制连接类型(内/外/左/右),但无需指定匹配列(直接以索引为关联键)。

单索引连接要求索引唯一(否则需先重置索引),多索引连接则支持多层索引(如按国家/股票分组的数据),扩展了维度灵活性。

相比 mergejoin因少参数(无需手动指定列)、索引预排序特性,在索引对齐场景(如时间序列数据)中性能更优;但 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'会丢弃非交集索引行(仅保留共有的索引)。

相比 mergeconcat逻辑更简单(无需匹配列),但灵活性较低(仅支持轴堆叠);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 类基础图表:

  1. 线状图:主要用于展示数据随时间的变化趋势,如股票每日收盘价的连续波动、月度销售额的变化曲线,通过 df.plot(kind='line') 实现,支持标注坐标轴、添加图例以清晰呈现趋势。
  2. 散点图:用于分析两个变量间的关联关系,如股票成交量与价格的相关性、广告投入与销量的对应关系,通过 df.plot(kind='scatter') 生成,可通过颜色、大小区分不同类别数据。
  3. 条形图:适用于对比不同类别数据的数值差异,如不同行业股票的平均收益率、不同产品的月度销量,通过 df.plot(kind='bar') 实现,支持横向 / 纵向条形图切换,适配不同类别名称长度的展示需求。
  4. 箱形图:用于展示数据的分布特征(如四分位数、异常值),如某股票价格的波动范围、不同区域用户消费金额的分布差异,通过 df.plot(kind='box') 生成,可快速识别数据中的异常值(如远超正常范围的极端价格)。
  5. 直方图:用于呈现数据的频率分布,如股票日收益率的分布情况、用户年龄的分布区间,通过 df.plot(kind='hist') 实现,支持调整分组数量(bins)以优化分布细节的展示。
  6. 饼状图:用于展示各部分占总体的比例关系,如不同板块股票在投资组合中的权重、不同渠道收入占总营收的比例,通过 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 度开始]

image.png

(九)数据处理

最后我们总结 Pandas 的核心内容,重点讲解数据处理的核心技术。需要注意的是,前面课程中使用的所有数据均已经过处理,呈现为“干净”状态;但在实际工作中,原始数据通常是“杂乱”的,因此数据预处理是分析的第一步。数据处理主要可分为两类:

  • 数据清洗(data cleansing):处理缺失值、离群值(异常值);
  • 数据转换(data transforming):包括编码、分组等操作。

核心模块一:数据清洗

数据清洗是处理数据的基础环节,核心是“剔除无效数据、修复不完整数据”,该部分重点介绍 4 类常见问题的解决方案,均结合 Pandas 实操方法,适配小白理解与应用:

  1. 缺失值处理:针对数据中“空值(NaN)”问题,提供两类核心策略——
    • 「删除法」:通过 dropna() 函数删除含缺失值的行或列(如删除股票数据中“收盘价”为空的记录),支持通过参数指定“删除含任意缺失值的行”或“删除全为缺失值的行”,避免无效数据干扰分析;
    • 「填补法」:通过 fillna() 函数用合理值填补缺失值(如用股票价格的“均值”“中位数”填补空值,或用前一天价格“向前填充”),适配不同场景下的缺失值修复需求,减少数据丢失。
  2. 重复值处理:针对数据中“完全重复或部分重复”的记录,通过 duplicated() 函数识别重复数据(返回布尔值标记是否重复),再用 drop_duplicates() 函数删除重复项,例如清理股票数据中“同一时间、同一股票代码”的重复记录,保证数据唯一性。
  3. 异常值处理:针对数据中“超出合理范围”的极端值(如股票价格突然出现的异常高值/低值),提供两类常用方法——
    • 「统计法」:结合描述性统计(如 describe() 查看四分位数),用“3σ 原则”或“箱形图法”识别异常值(如超出上下四分位数 1.5 倍四分位距的值);
    • 「过滤法」:通过布尔索引筛选出合理范围的数据(如 df[df['价格'] < 1000] 剔除股票价格超 1000 的异常值),或用 clip() 函数将极端值限制在合理区间内。
  4. 格式与内容纠错:针对数据“格式不统一”(如日期格式混乱、文本大小写不一致)或“内容错误”(如股票代码多写字符)的问题,通过 Pandas 字符串方法(如 str.strip() 去除多余空格、str.upper() 统一文本大写)、日期转换函数(如 pd.to_datetime() 规范日期格式)修正,确保数据格式符合分析要求。

核心模块二:数据转换

数据转换是为了“让数据结构/格式适配分析场景”,该部分重点介绍 3 类高频转换操作,均结合实际场景(如股票数据处理)说明用法:

  1. 数据类型转换:针对数据类型不匹配的问题(如“价格”字段误存为文本类型),通过 astype() 函数转换数据类型(如 df['价格'].astype('float') 将文本转为数值型),或用 pd.to_numeric() 处理含非数字字符的数值字段(如 pd.to_numeric(df['成交量'], errors='coerce') 将无法转换的值设为 NaN),确保后续统计计算正常进行。
  2. 数据格式重塑:针对数据结构不便于分析的问题(如“宽表”转“长表”、行列互换),衔接前文“数据重塑”内容,重点应用 melt()(将多列数据“熔化”为长表,如将多只股票的价格列转为“股票代码-价格”的两列结构)、pivot()(将长表“透视”为宽表,如将“日期-股票代码-价格”的长表转为“日期为行、股票代码为列”的宽表),适配不同分析场景下的数据结构需求。
  3. 自定义转换:针对个性化分析需求(如股票收益率计算、数据分组映射),通过 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 的一些高级用法和其他工具库。

最后一次编辑于 3天前 2

暂无评论