3、NumPy基础:ndarray数组的创建、属性、数据类型与索引切片
NumPy(Numerical Python)是Python科学计算生态的基石,而Pandas底层的数据结构(Series和DataFrame)也大量依赖NumPy的ndarray数组。可以毫不夸张地说,没有扎实的NumPy功底,后续的Pandas数据处理、数据清洗和统计分析都将如履薄冰。本章将系统讲解ndarray数组的创建方式、核心属性(shape、dtype、size)、数据类型体系与转换规则,以及最关键的索引与切片操作。这些内容构成了数据分析中“数据容器”与“数据访问”两大基本功,掌握它们,你才能高效地操作批量数据,并为后续学习Pandas打下坚实基础。
上图清晰地展示了本章的知识体系:从创建数组开始,到理解其属性,再到数据类型管理,最后掌握索引切片技巧,并最终串联成一个完整的数据分析实战闭环。下面我们逐一深入每个模块。
3.1 ndarray数组的创建:从零构建你的数据容器
核心原理
ndarray(N-dimensional array)是NumPy中用于存储同质数据(即所有元素类型相同)的多维数组对象。与Python原生列表(list)不同,ndarray在内存中是连续存储的,这使得向量化运算成为可能,从而大幅提升计算性能。创建ndarray是数据分析的第一步,选择正确的创建方式直接影响后续操作的效率和代码的可读性。
操作流程与常用方法
NumPy提供了多种创建数组的途径,我们可以根据数据来源和需求选择合适的方法:
| 创建函数 | 描述 | 典型示例 | 输出结果 |
|---|---|---|---|
| np.array() | 从Python列表或元组创建 | np.array([[1,2],[3,4]]) | 2×2整数数组 |
| np.arange() | 类似range(),生成等差序列 | np.arange(0, 10, 2) | [0, 2, 4, 6, 8] |
| np.linspace() | 生成指定数量的等间隔点 | np.linspace(0, 1, 5) | [0., 0.25, 0.5, 0.75, 1.] |
| np.zeros() / np.ones() | 生成全0或全1数组 | np.zeros((2,3)) | 2×3浮点数组 |
| np.eye() | 生成单位矩阵 | np.eye(3) | 3×3对角为1的矩阵 |
| np.random.rand() | 生成[0,1)均匀分布随机数 | np.random.rand(2,2) | 2×2随机数组 |
在实际操作中,np.array()是最灵活的方式,因为它允许我们从已有的Python数据结构直接转换。而np.arange()和np.linspace()则常用于生成连续数值序列,例如时间轴或坐标轴。
案例拆解:假设我们需要创建一个包含某商品一周(7天)每日销售量的数组,并初始化一个3天的预测数组。
import numpy as np
# 实际销售量(单位:件)
sales_actual = np.array([120, 135, 98, 145, 160, 142, 130])
# 未来3天预测值初始化为0
sales_forecast = np.zeros(3, dtype=int)
print("实际销量:", sales_actual)
print("预测占位:", sales_forecast)
print("数据类型:", sales_actual.dtype, sales_forecast.dtype)
输出:实际销量: [120 135 98 145 160 142 130],预测占位: [0 0 0]。这里我们通过dtype=int显式指定了整数类型,避免了默认的浮点类型。
常见误区与总结
- 误区1:使用
np.array()时传入不规则嵌套列表(如[[1,2],[3]]),会导致创建一维对象数组而非二维数组,后续运算会报错。 - 误区2:混淆
np.arange()和np.linspace()。前者指定步长,后者指定数量,二者参数含义不同。 - 总结:创建数组时,先明确数据的维度(1D、2D还是更高维)和数据类型,再选择最简洁的创建函数。
3.2 ndarray核心属性:shape、dtype与size
核心原理
ndarray的三个核心属性——shape、dtype、size——分别描述了数组的“形状”、“类型”和“大小”。理解这三个属性是进行任何数组操作的前提。shape是一个元组,表示每个维度的大小;dtype定义了元素的存储类型,直接影响内存占用和计算精度;size则是所有维度大小的乘积,即元素总数。
属性详解与判断方法
- shape(形状):例如
(3, 4)表示3行4列的二维数组,(2, 3, 4)表示2个3×4的三维数组。可以通过ndim属性获取维度数量。 - dtype(数据类型):常见的有
int32、int64、float32、float64、bool等。默认情况下,Python整数会映射为int64,浮点数映射为float64。 - size(元素总数):等于
shape中所有数字的乘积,例如(3,4)的size为12。
经验提醒:在数据分析中,我们经常需要检查数据的形状是否符合预期。例如,从CSV文件加载数据后,第一件事就是打印data.shape来确认行数和列数。如果shape不符合预期,后续的切片和聚合操作很可能出错。
案例拆解:销售数据体检
假设我们加载了一个包含4个季度、3个产品线的销售数据数组,需要快速了解其结构。
sales_data = np.array([[100, 150, 200],
[110, 160, 210],
[120, 170, 220],
[130, 180, 230]])
print("形状 shape:", sales_data.shape) # (4, 3)
print("维度 ndim:", sales_data.ndim) # 2
print("元素总数 size:", sales_data.size) # 12
print("数据类型 dtype:", sales_data.dtype) # int64
通过这一组属性,我们立即知道这是一个4行3列的二维数组,包含12个整数元素。这为后续按季度(行)或按产品(列)进行切片分析提供了基础。
常见误区与总结
- 误区:误以为
size返回的是“行数”或“列数”,实际上它是所有元素的个数。 - 误区:忽略
dtype导致的内存问题。例如,一个包含1000万个浮点数的数组,float64占用80MB,而float32仅占用40MB。在内存受限的环境中,合理选择dtype至关重要。 - 总结:每次创建或加载数组后,养成检查
shape和dtype的习惯,这是数据质量检查的第一步。
3.3 数据类型与转换:精确控制你的数据精度
核心原理
NumPy的数据类型体系比Python原生类型更丰富,它支持不同位数的整数(int8、int16、int32、int64)、无符号整数(uint8等)、浮点数(float16、float32、float64)以及复数(complex64、complex128)。数据类型决定了数值的表示范围和精度。例如,int8的范围是-128到127,超出范围会导致溢出错误;float32的精度约为7位十进制数字,而float64约为15位。
转换方法与操作流程
使用astype()方法可以显式转换数据类型。转换时需要注意:
- 从浮点转换为整数时,小数部分会被截断(不是四舍五入)。
- 从大范围类型转换为小范围类型时,可能发生溢出或精度丢失。
- 字符串类型(如
'int32')也可以作为astype()的参数。
prices = np.array([19.99, 25.50, 30.75])
prices_int = prices.astype(int) # 截断为 [19, 25, 30]
prices_float32 = prices.astype('float32') # 降低精度
print(prices_int, prices_float32.dtype)
风险提示:在金融计算中,使用float32可能导致累积误差。例如,计算大量交易金额的总和时,float32的精度不足可能产生“分”级别的误差。建议在涉及金额的场景中使用float64或Decimal。
案例拆解:图像数据处理中的类型转换
在图像处理中,像素值通常以uint8(0-255)存储。如果我们需要进行浮点运算(如归一化),必须先转换类型。
img_data = np.array([[255, 128, 0],
[64, 32, 16]], dtype=np.uint8)
# 归一化到[0,1]区间
img_normalized = img_data.astype(np.float64) / 255.0
print(img_normalized)
# 再转换回uint8用于显示
img_restored = (img_normalized * 255).astype(np.uint8)
print(img_restored)
这个案例展示了类型转换在数据预处理中的典型应用:先提升精度进行计算,再降回原始类型以节省存储空间。
常见误区与总结
- 误区:使用
astype()时,原数组不会被修改,必须重新赋值。忘记赋值是常见错误。 - 误区:将字符串数组直接转换为数值类型时,如果字符串包含非数字字符(如"$100"),会抛出
ValueError。需要先清洗数据。 - 总结:类型转换是数据预处理的核心环节,务必明确目标类型和转换规则,避免精度损失和溢出。
3.4 数组索引与切片:精准定位你的数据
核心原理
索引(Indexing)和切片(Slicing)是访问ndarray元素的核心手段。NumPy支持多种索引方式:整数索引、切片索引、布尔索引和花式索引(整数数组索引)。理解“视图(view)”与“副本(copy)”的区别是避免数据意外修改的关键。
操作流程与技巧
- 一维数组切片:
arr[start:stop:step],与Python列表切片规则一致。 - 多维数组索引:使用逗号分隔各维度的索引,例如
arr[1, 2]表示第1行第2列的元素。 - 切片返回视图:对切片结果的修改会影响原数组,因为切片是原数组的引用(视图)。
- 布尔索引:通过条件表达式生成布尔数组,再作为索引,例如
arr[arr > 5]。
data = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
# 提取第2行(索引为1)
row_2 = data[1, :] # 视图
# 提取第1列和第3列的所有行
cols_1_3 = data[:, [0, 2]]
# 布尔索引:找出所有大于5的元素
mask = data > 5
filtered = data[mask]
print("第2行:", row_2)
print("第1和3列:\n", cols_1_3)
print("大于5的元素:", filtered)
实战场景:销售数据分析中的切片应用
假设我们有一个包含12个月销售数据的数组,需要分析下半年(7-12月)的销售趋势,并找出销量超过150的月份。
monthly_sales = np.array([120, 135, 98, 145, 160, 142,
130, 155, 170, 165, 180, 190])
# 下半年切片
h2_sales = monthly_sales[6:12]
# 高销量月份布尔索引
high_sales = monthly_sales[monthly_sales > 150]
print("下半年销量:", h2_sales)
print("高销量月份数据:", high_sales)
# 注意:h2_sales是视图,修改它会影响原数组
h2_sales[0] = 0
print("原数组也被修改:", monthly_sales[6]) # 输出0
这个案例展示了切片和布尔索引的联合使用。特别要注意的是,切片返回的是视图,修改h2_sales会改变monthly_sales。如果需要独立副本,必须使用.copy()方法。
常见误区与总结
- 误区1:混淆视图与副本。对视图的修改会“污染”原数据,这在数据清洗时可能造成不可逆的错误。
- 误区2:在布尔索引中忘记加括号。例如
arr[(arr > 5) & (arr < 10)],如果写成arr[arr > 5 & arr < 10]会因运算符优先级问题报错。 - 误区3:使用链式索引(如
arr[1][2])虽然可行,但效率低于arr[1, 2],且容易产生视图混淆。 - 总结:索引与切片是数据操作的“手术刀”,务必明确操作返回的是视图还是副本,并善用布尔索引进行条件筛选。
复盘与最佳实践:在本章中,我们系统地学习了ndarray的创建、属性、类型转换和索引切片。在实际数据分析项目中,建议遵循以下流程:① 使用np.array()或np.loadtxt()等加载数据;② 立即检查shape和dtype;③ 根据分析需求使用astype()调整数据类型;④ 使用切片和布尔索引提取目标数据子集;⑤ 在修改数据前,明确是否需要.copy()。掌握这些基础操作,你将能够高效地处理批量数据,并为后续Pandas的学习铺平道路。