数据分析
数据分析是从数据中提取有价值信息的过程。Python 的数据分析生态系统以三大核心库为基础:
- NumPy:提供高性能的多维数组对象和数学运算能力
- Pandas:提供灵活的数据结构(DataFrame/Series)和数据操作工具
- Matplotlib/Pyecharts:提供强大的数据可视化能力
这三个库相互配合,构成了 Python 数据分析的完整工作流:数据处理 → 数据分析 → 数据可视化。
import numpy as np # 数值计算
import pandas as pd # 数据处理
import matplotlib.pyplot as plt # 数据可视化
NumPy
NumPy(Numerical Python)是 Python 科学计算的基础库。它提供了高性能的多维数组对象(ndarray)和用于处理这些数组的工具。
NumPy 的核心优势:
- 高效的数组运算:底层用 C 实现,比 Python 列表快 10-100 倍
- 广播机制:不同形状的数组也能进行 运算
- 丰富的数学函数:线性代数、傅里叶变换、随机数生成等
- 生态系统基石:Pandas、SciPy、Scikit-learn 等都基于 NumPy
基础入门
导入和创建数组
# 推荐的导入方式
import numpy as np
# 其他导入方式(不推荐)
import numpy # 需要写 numpy.array,太长
from numpy import * # 污染命名空间
from numpy import array, sin # 只导入部分函数
推荐使用 import numpy as np,这是 NumPy 官方文档和社区的标准约定。
为什么使用 NumPy 而不是 Python 列表?
# Python 列表的局限性
a = [1, 2, 3]
# a + 1 # TypeError: 列表不支持与整数相加
# 需要使用列表推导式
b = [x + 1 for x in a]
print(b) # [2, 3, 4]
# NumPy 数组支持向量化运算
import numpy as np
a = np.array([1, 2, 3])
b = a + 1
print(b) # [2 3 4]
# 数组间的运算(对应元素)
c = a + b
print(c) # [3 5 7]
# 对应元素相乘
print(a * b) # [2 6 12]
# 对应元素乘方
print(a ** b) # [1 8 81]
NumPy 的性能优势:
# Python 列表:需要循环
result = []
for i in range(1000000):
result.append(i * 2)
# NumPy:向量化运算,快 10-100 倍
arr = np.arange(1000000)
result = arr * 2
数组属性
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a)
# [[1 2 3]
# [4 5 6]]
# 查看形状(返回元组,每个元素代表该维度的元素数目)
print(a.shape) # (2, 3) - 2行3列
# 查看总元素数
print(a.size) # 6
# 查看维度
print(a.ndim) # 2
# 查看数据类型
print(a.dtype) # int64 或 int32
# 查看每个元素的字节大小
print(a.itemsize) # 8 (64位整数)
常用属性速查:
shape:数组形状,如 (3, 4) 表示3行4列size:总元素个数ndim:维度数(1维、2维、3维...)dtype:数据类型(int64, float64等)itemsize:每个元素占用的字节数
创建数组的多种方式
import numpy as np
# 从列表创建
a = np.array([1, 2, 3, 4])
print(a) # [1 2 3 4]
# 从元组创建
b = np.array((1, 2, 3, 4))
print(b) # [1 2 3 4]
# 创建二维数组
c = np.array([[1, 2], [3, 4]])
print(c)
# [[1 2]
# [3 4]]
# 指定数据类型
d = np.array([1, 2, 3], dtype=float)
print(d) # [1. 2. 3.]
# 创建全零数组
zeros = np.zeros((2, 3))
print(zeros)
# [[0. 0. 0.]
# [0. 0. 0.]]
# 创建全一数组
ones = np.ones((2, 3))
print(ones)
# [[1. 1. 1.]
# [1. 1. 1.]]
# 创建单位矩阵
eye = np.eye(3)
print(eye)
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# 创建指定值的数组
full = np.full((2, 3), 7)
print(full)
# [[7 7 7]
# [7 7 7]]
数组索引和切片
一维数组索引
import numpy as np
a = np.array([1, 2, 3, 4, 5])
# 基本索引
print(a[0]) # 1 - 第一个元素
print(a[-1]) # 5 - 最后一个元素
# 切片(左闭右开)
print(a[:2]) # [1 2] - 前两个元素
print(a[2:]) # [3 4 5] - 从第3个到最后
print(a[1:4]) # [2 3 4] - 第2到第4个
print(a[::2]) # [1 3 5] - 步长为2
print(a[::-1]) # [5 4 3 2 1] - 反转
# 修改元素
a[0] = 100
print(a) # [100 2 3 4 5]
多维数组索引
import numpy as np
a = np.array([[0, 1, 2, 3, 4, 5],
[10, 11, 12, 13, 14, 15],
[20, 21, 22, 23, 24, 25],
[30, 31, 32, 33, 34, 35],
[40, 41, 42, 43, 44, 45],
[50, 51, 52, 53, 54, 55]])
# 访问单个元素
print(a[0, 3]) # 3 - 第0行第3列
# 访问整行
print(a[0]) # [ 0 1 2 3 4 5]
# 访问整列
print(a[:, 2]) # [ 2 12 22 32 42 52]
# 行切片
print(a[0:2]) # 前两行
# [[ 0 1 2 3 4 5]
# [10 11 12 13 14 15]]
# 列切片
print(a[:, 3:5]) # 所有行的第3-4列
# [[ 3 4]
# [13 14]
# [23 24]
# [33 34]
# [43 44]
# [53 54]]
# 组合切片
print(a[0, 3:5]) # 第0行的第3-4列 [3 4]
print(a[4:, 4:]) # 最后两行的最后两列
# [[44 45]
# [54 55]]
# 步长切片
print(a[2::2, ::2]) # 从第2行开始每隔一行,每隔一列
# [[20 22 24]
# [40 42 44]]
切片与引用: NumPy 数组的切片是引用,不是复制!修改切片会影响原数组。
a = np.array([1, 2, 3, 4, 5])
b = a[2:4] # b 是 a 的视图
b[0] = 100 # 修改 b
print(a) # [ 1 2 100 4 5] - a 也被修改了!
# 使用 copy() 创建副本
c = a[2:4].copy()
c[0] = 200
print(a) # [ 1 2 100 4 5] - a 不受影响
花式索引
使用数组或列表作为索引:
import numpy as np
a = np.arange(0, 80, 10) # [ 0 10 20 30 40 50 60 70]
# 使用索引数组
indices = [1, 2, -3]
print(a[indices]) # [10 20 50]
# 使用索引列表
print(a[[0, 2, 4]]) # [ 0 20 40]
# 多维数组的花式索引
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# 选择第0行和第2行
print(a[[0, 2]])
# [[ 0 1 2 3]
# [ 8 9 10 11]]
# 选择特定的行列组合
rows = [0, 1, 2]
cols = [1, 2, 3]
print(a[rows, cols]) # [ 1 6 11]
布尔索引
使用布尔数组进行条件筛选:
import numpy as np
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# 创建布尔mask
mask = a > 5
print(mask)
# [False False False False False True True True True True]
# 使用布尔索引
print(a[mask]) # [ 6 7 8 9 10]
# 直接使用条件
print(a[a > 5]) # [ 6 7 8 9 10]
print(a[a % 2 == 0]) # [ 2 4 6 8 10] - 偶数
# 组合条件
print(a[(a > 3) & (a < 8)]) # [4 5 6 7]
print(a[(a < 3) | (a > 8)]) # [ 1 2 9 10]
# 修改满足条件的元素
a[a > 5] = 0
print(a) # [1 2 3 4 5 0 0 0 0 0]
布尔运算符:
&:与(and)|:或(or)~:非(not)
注意要使用 & 和 |,而不是 and 和 or,并且需要用括号!
where 函数
where 函数返回满足条件的元素索引:
import numpy as np
a = np.array([1, 2, 4, 6, 8])
# 查找满足条件的索引
indices = np.where(a > 2)
print(indices) # (array([2, 3, 4]),)
print(indices[0]) # [2 3 4]
# 使用索引取值
print(a[indices]) # [4 6 8]
# 二维数组的 where
a = np.array([[0, 12, 5, 20],
[1, 2, 11, 15]])
rows, cols = np.where(a > 10)
print(rows) # [0 0 1 1]
print(cols) # [1 3 2 3]
print(a[rows, cols]) # [12 20 11 15]
# where 的三元运算用法
a = np.array([1, 2, 3, 4, 5])
result = np.where(a > 3, '大', '小')
print(result) # ['小' '小' '小' '大' '大']
数组运算
基本数学运算
import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
# 加法
print(a + b) # [11 22 33 44]
print(a + 10) # [11 12 13 14] - 广播
# 减法
print(b - a) # [ 9 18 27 36]
print(a - 1) # [0 1 2 3]
# 乘法(对应元素)
print(a * b) # [ 10 40 90 160]
print(a * 2) # [2 4 6 8]
# 除法
print(b / a) # [10. 10. 10. 10.]
print(a / 2) # [0.5 1. 1.5 2. ]
# 整除
print(b // a) # [10 10 10 10]
# 求余
print(b % a) # [0 0 0 0]
# 幂运算
print(a ** 2) # [ 1 4 9 16]
print(2 ** a) # [ 2 4 8 16]
广播机制
广播(Broadcasting)允许不同形状的数组进行运算:
import numpy as np
# 一维和标量
a = np.array([1, 2, 3])
print(a + 10) # [11 12 13]
# 二维和一维
a = np.array([[1, 2, 3],
[4, 5, 6]])
b = np.array([10, 20, 30])
print(a + b)
# [[11 22 33]
# [14 25 36]]
# 二维和列向量
a = np.array([[1, 2, 3],
[4, 5, 6]])
b = np.array([[10],
[20]])
print(a + b)
# [[11 12 13]
# [24 25 26]]
# 理解广播规则
# 规则1:如果两个数组维度不同,较小维度数组会在前面补1
# 规则2:如果某个维度大小不同,大小为1的会被"拉伸"以匹配另一个
广播规则简记:
- 如果两个数组形状在某个维度上一致或其中一个为1,则兼容
- 维度数较小的数组会在前面补1
- 输出形状是每个维度的最大值
a: (3, 4) b: (4,) -> (3, 4)
a: (3, 1) b: (3, 4) -> (3, 4)
a: (2, 3, 4) b: (3, 1) -> (2, 3, 4)
通用函数(ufunc)
import numpy as np
a = np.array([1, 4, 9, 16])
# 数学函数
print(np.sqrt(a)) # [1. 2. 3. 4.] - 平方根
print(np.square(a)) # [ 1 16 81 256] - 平方
print(np.exp(a)) # [2.71828183e+00 5.45981500e+01 ...]
print(np.log(a)) # [0. 1.38629436 2.19722458 2.77258872]
print(np.log10(a)) # [0. 0.60205999 0.95424251 1.20411998]
# 三角函数
angles = np.array([0, np.pi/2, np.pi])
print(np.sin(angles)) # [ 0.0000000e+00 1.0000000e+00 1.2246468e-16]
print(np.cos(angles)) # [ 1.000000e+00 6.123234e-17 -1.000000e+00]
print(np.tan(angles)) # [ 0.00000000e+00 1.63312394e+16 -1.22464680e-16]
# 取整函数
a = np.array([1.2, 2.5, 3.7, 4.9])
print(np.floor(a)) # [1. 2. 3. 4.] - 向下取整
print(np.ceil(a)) # [2. 3. 4. 5.] - 向上取整
print(np.round(a)) # [1. 2. 4. 5.] - 四舍五入
# 绝对值
a = np.array([-1, -2, 3, -4])
print(np.abs(a)) # [1 2 3 4]
print(np.absolute(a)) # [1 2 3 4] - 同上
# 符号函数
print(np.sign(a)) # [-1 -1 1 -1]
聚合函数
import numpy as np
a = np.array([[1, 2, 3],
[4, 5, 6]])
# 求和
print(np.sum(a)) # 21 - 所有元素求和
print(a.sum()) # 21 - 同上
print(a.sum(axis=0)) # [5 7 9] - 按列求和
print(a.sum(axis=1)) # [ 6 15] - 按行求和
# 平均值
print(np.mean(a)) # 3.5
print(a.mean(axis=0)) # [2.5 3.5 4.5]
print(a.mean(axis=1)) # [2. 5.]
# 最大最小值
print(a.max()) # 6
print(a.min()) # 1
print(a.max(axis=0)) # [4 5 6]
print(a.min(axis=1)) # [1 4]
# 最大最小值的索引
print(a.argmax()) # 5 - 扁平化后的索引
print(a.argmin()) # 0
print(a.argmax(axis=0)) # [1 1 1]
print(a.argmax(axis=1)) # [2 2]
# 标准差和方差
print(a.std()) # 1.707825127659933
print(a.var()) # 2.9166666666666665
# 累积和和累积积
print(np.cumsum(a)) # [ 1 3 6 10 15 21]
print(np.cumprod(a)) # [ 1 2 6 24 120 720]
axis 参数理解:
axis=None:对所有元素操作(默认)axis=0:沿着第0维(列),对每列操作axis=1:沿着第1维(行),对每行操作
记忆技巧:axis=0 会让该维度"消失",结果是剩余维度的结果
条件筛选
import numpy as np
a = np.array([1, 2, 3, 4, 5, 6])
# clip:限制在范围内
print(np.clip(a, 2, 5)) # [2 2 3 4 5 5]
# 判断函数
print(np.all(a > 0)) # True - 是否全部满足
print(np.any(a > 5)) # True - 是否有任意元素满足
# 最大最小值比较
a = np.array([1, 5, 3])
b = np.array([2, 4, 6])
print(np.maximum(a, b)) # [2 5 6] - 逐元素取最大
print(np.minimum(a, b)) # [1 4 3] - 逐元素取最小
数组操作
形状操作
import numpy as np
a = np.arange(12)
print(a) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(a.shape) # (12,)
# reshape:改变形状(不改变原数组)
b = a.reshape(3, 4)
print(b)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a.shape) # (12,) - 原数组未改变
# resize:改变形状(改变原数组)
a.resize(3, 4)
print(a.shape) # (3, 4) - 原数组已改变
# reshape 的 -1 参数(自动计算)
a = np.arange(12)
print(a.reshape(3, -1)) # (3, 4) - 自动计算为4
print(a.reshape(-1, 4)) # (3, 4) - 自动计算为3
print(a.reshape(2, 2, -1)) # (2, 2, 3)
# flatten:展平为一维(复制)
a = np.array([[1, 2], [3, 4]])
b = a.flatten()
b[0] = 100
print(a[0, 0]) # 1 - 原数组未改变
# ravel:展平为一维(视图)
b = a.ravel()
b[0] = 100
print(a[0, 0]) # 100 - 原数组被改变
# 转置
a = np.array([[1, 2, 3],
[4, 5, 6]])
print(a.T)
# [[1 4]
# [2 5]
# [3 6]]
# squeeze:去除长度为1的维度
a = np.arange(10).reshape(1, 10)
print(a.shape) # (1, 10)
b = np.squeeze(a)
print(b.shape) # (10,)
# expand_dims:增加维度
a = np.array([1, 2, 3])
b = np.expand_dims(a, axis=0)
print(b.shape) # (1, 3)
c = np.expand_dims(a, axis=1)
print(c.shape) # (3, 1)
reshape vs resize vs ravel vs flatten:
reshape:返回新视图,不改变原数组resize:直接修改原数组ravel:返回展平的视图(可能影响原数组)flatten:返回展平的副本(不影响原数组)
a = np.array([[1, 2], [3, 4]])
b = a.reshape(4) # 新视图
c = a.ravel() # 展平视图
d = a.flatten() # 展平副本
c[0] = 100
print(a[0, 0]) # 100 - ravel 影响原数组
d[0] = 200
print(a[0, 0]) # 100 - flatten 不影响原数组
数组合并
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# concatenate:沿指定轴连接
print(np.concatenate([a, b], axis=0)) # 垂直拼接
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
print(np.concatenate([a, b], axis=1)) # 水平拼接
# [[1 2 5 6]
# [3 4 7 8]]
# vstack:垂直堆叠(相当于 axis=0)
print(np.vstack([a, b]))
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
# hstack:水平堆叠(相当于 axis=1)
print(np.hstack([a, b]))
# [[1 2 5 6]
# [3 4 7 8]]
# stack:沿新轴堆叠
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.stack([a, b])) # axis=0(默认)
# [[1 2 3]
# [4 5 6]]
print(np.stack([a, b], axis=1)) # axis=1
# [[1 4]
# [2 5]
# [3 6]]
# append:追加元素(返回新数组)
a = np.array([1, 2, 3])
b = np.append(a, [4, 5, 6])
print(b) # [1 2 3 4 5 6]
# 二维数组的 append
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
print(np.append(a, b, axis=0))
# [[1 2]
# [3 4]
# [5 6]]
数组合并方法选择:
- 垂直拼接(增加行):
vstack或concatenate(axis=0) - 水平拼接(增加列):
hstack或concatenate(axis=1) - 新维度堆叠:
stack - 简单追加:
append(但效率较低)
数组分割
import numpy as np
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# split:均等分割
parts = np.split(a, 3, axis=0) # 分成3部分
for i, part in enumerate(parts):
print(f"Part {i}:\n{part}")
# vsplit:垂直分割(按行)
parts = np.vsplit(a, 3) # 分成3行
# hsplit:水平分割(按列)
parts = np.hsplit(a, [1, 3]) # 在列1和列3处分割
# 结果:[:, :1], [:, 1:3], [:, 3:]
# array_split:不均等分割
a = np.arange(10)
parts = np.array_split(a, 3) # 分成3部分(不均等)
print(parts[0]) # [0 1 2 3]
print(parts[1]) # [4 5 6]
print(parts[2]) # [7 8 9]
数组复制
import numpy as np
a = np.array([1, 2, 3, 4])
# 赋值(共享内存)
b = a
b[0] = 100
print(a[0]) # 100 - a 被改变
# view(浅拷贝,共享数据)
a = np.array([1, 2, 3, 4])
b = a.view()
b[0] = 100
print(a[0]) # 100 - a 被改变
b.shape = (2, 2)
print(a.shape) # (4,) - 形状独立
# copy(深拷贝,独立数据)
a = np.array([1, 2, 3, 4])
b = a.copy()
b[0] = 100
print(a[0]) # 1 - a 不受影响
三种复制方式的区别:
| 方式 | 内存 | 数据修改 | 形状修改 |
|---|---|---|---|
b = a | 共享 | 互相影响 | 互相影响 |
b = a.view() | 共享 | 互相影响 | 不影响 |
b = a.copy() | 独立 | 不影响 | 不影响 |
建议:
- 只是引用用赋值
- 需要不同形状但共享数据用 view
- 需要完全独立的副本用 copy
数组创建函数
NumPy 提供了多种便捷的数组创建函数。
序列生成
import numpy as np
# arange:类似 range,生成等差数列
print(np.arange(5)) # [0 1 2 3 4]
print(np.arange(2, 10)) # [2 3 4 5 6 7 8 9]
print(np.arange(0, 10, 2)) # [0 2 4 6 8]
print(np.arange(0, 1, 0.1)) # [0. 0.1 0.2 ... 0.9]
# linspace:生成指定数量的等差数列(包含终点)
print(np.linspace(0, 1, 5)) # [0. 0.25 0.5 0.75 1. ]
print(np.linspace(0, 10, 11)) # [ 0. 1. 2. ... 10.]
print(np.linspace(0, 2*np.pi, 5)) # [0. 1.57 3.14 4.71 6.28]
# logspace:生成对数等差数列
print(np.logspace(0, 2, 5)) # [ 1. 3.16 10. 31.62 100. ]
# 相当于 [10^0, 10^0.5, 10^1, 10^1.5, 10^2]
arange vs linspace:
arange(start, stop, step):指定步长,不包含终点linspace(start, stop, num):指定数量,包含终点
# 生成0到10的数
arange(0, 10, 1) # [0 1 2 3 4 5 6 7 8 9] - 10个数
linspace(0, 10, 11) # [0 1 2 3 4 5 6 7 8 9 10] - 11个数
特殊数组
import numpy as np
# 零数组
print(np.zeros(5)) # [0. 0. 0. 0. 0.]
print(np.zeros((2, 3))) # 2x3 零矩阵
# 一数组
print(np.ones(5)) # [1. 1. 1. 1. 1.]
print(np.ones((2, 3))) # 2x3 一矩阵
# 指定值数组
print(np.full(5, 7)) # [7 7 7 7 7]
print(np.full((2, 3), 3.14)) # 2x3 矩阵,值都是3.14
# 单位矩阵
print(np.eye(3))
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# 对角矩阵
print(np.diag([1, 2, 3]))
# [[1 0 0]
# [0 2 0]
# [0 0 3]]
# 空数组(未初始化)
print(np.empty((2, 3))) # 值是随机的(内存中的残留值)
# 类似现有数组的形状
a = np.array([[1, 2], [3, 4]])
print(np.zeros_like(a)) # 形状相同的零数组
print(np.ones_like(a)) # 形状相同的一数组
print(np.full_like(a, 7)) # 形状相同,值为7
随机数组
import numpy as np
# 设置随机种子(保证可重复性)
np.random.seed(42)
# 均匀分布 [0, 1)
print(np.random.rand(5)) # 一维
print(np.random.rand(2, 3)) # 二维
# 标准正态分布(均值0,标准差1)
print(np.random.randn(5)) # 一维
print(np.random.randn(2, 3)) # 二维
# 指定范围的整数
print(np.random.randint(0, 10, 5)) # [0, 10) 的5个整数
print(np.random.randint(1, 100, (2, 3))) # 2x3 矩阵
# 指定范围的均匀分布
print(np.random.uniform(0, 10, 5)) # [0, 10) 的均匀分布
# 正态分布(指定均值和标准差)
print(np.random.normal(5, 2, 10)) # 均值5,标准差2,10个数
# 从数组中随机选择
a = np.array([1, 2, 3, 4, 5])
print(np.random.choice(a, 3)) # 随机选3个(可重复)
print(np.random.choice(a, 3, replace=False)) # 不重复
# 打乱数组
np.random.shuffle(a) # 原地打乱
print(a)
# 随机排列
print(np.random.permutation(10)) # [0, 10) 的随机排列
新版随机数生成(推荐):
NumPy 1.17+ 推荐使用 np.random.Generator:
rng = np.random.default_rng(42) # 新式随机数生成器
rng.random(5) # 替代 rand
rng.standard_normal(5) # 替代 randn
rng.integers(0, 10, 5) # 替代 randint
rng.choice(a, 3) # 替代 choice
网格生成
import numpy as np
# meshgrid:生成网格坐标
x = np.linspace(-1, 1, 3) # [-1. 0. 1.]
y = np.linspace(-1, 1, 3) # [-1. 0. 1.]
X, Y = np.meshgrid(x, y)
print(X)
# [[-1. 0. 1.]
# [-1. 0. 1.]
# [-1. 0. 1.]]
print(Y)
# [[-1. -1. -1.]
# [ 0. 0. 0.]
# [ 1. 1. 1.]]
# 常用于3D绘图或计算
Z = np.sqrt(X**2 + Y**2) # 计算每个点到原点的距离
print(Z)
# [[1.41421356 1. 1.41421356]
# [1. 0. 1. ]
# [1.41421356 1. 1.41421356]]
数据IO
NumPy 支持多种文件格式的读写。
文本文件
import numpy as np
# 创建示例数据
data = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 保存为文本文件
np.savetxt('data.txt', data)
# 默认使用科学计数法:1.000000e+00 2.000000e+00 ...
# 指定格式
np.savetxt('data.txt', data, fmt='%d') # 整数格式
np.savetxt('data.txt', data, fmt='%.2f') # 保留2位小数
# 指定分隔符
np.savetxt('data.csv', data, delimiter=',', fmt='%d')
# 读取文本文件
loaded = np.loadtxt('data.txt')
print(loaded)
# 读取CSV
loaded = np.loadtxt('data.csv', delimiter=',')
print(loaded)
# genfromtxt:更强大的文本读取(可处理缺失值)
data_with_header = """
Name,Age,Score
Alice,25,85.5
Bob,30,92.0
Charlie,28,78.5
"""
# 保存到文件
with open('students.csv', 'w') as f:
f.write(data_with_header)
# 读取(跳过标题行)
data = np.genfromtxt('students.csv',
delimiter=',',
skip_header=1,
usecols=(1, 2), # 只读取年龄和分数
dtype=float)
print(data)
# [[25. 85.5]
# [30. 92. ]
# [28. 78.5]]
loadtxt vs genfromtxt:
loadtxt:快速,适合简单规整的数据genfromtxt:功能强大,可处理缺失值、注释、复杂格式
常用参数:
delimiter:分隔符(默认空格)skip_header:跳过开头几行usecols:选择读取哪些列dtype:指定数据类型
二进制文件
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]])
# 保存单个数组(.npy 格式)
np.save('array.npy', a)
# 读取
loaded = np.load('array.npy')
print(loaded)
# 保存多个数组(.npz 格式,无压缩)
b = np.array([7, 8, 9])
np.savez('arrays.npz', array1=a, array2=b)
# 读取
data = np.load('arrays.npz')
print(data['array1']) # [[1 2 3] [4 5 6]]
print(data['array2']) # [7 8 9]
print(data.files) # ['array1', 'array2']
# 保存多个数组(压缩格式)
np.savez_compressed('arrays_compressed.npz', array1=a, array2=b)
# 比较文件大小
import os
print(f"无压缩: {os.path.getsize('arrays.npz')} bytes")
print(f"压缩: {os.path.getsize('arrays_compressed.npz')} bytes")
文件格式选择:
-
文本文件 (.txt, .csv):
- 优点:可读、可用其他工具打开
- 缺点:文件大、读写慢
- 适用:小数据、需要可读性
-
二进制文件 (.npy, .npz):
- 优点:文件小、读写快、保留精度
- 缺点:需要NumPy才能读取
- 适用:大数据、Python内部使用
# 大小对比
import numpy as np
a = np.arange(10000.)
np.savetxt('a.txt', a)
np.save('a.npy', a)
import os
print(f"文本文件: {os.path.getsize('a.txt')} bytes") # ~150KB
print(f"二进制文件: {os.path.getsize('a.npy')} bytes") # ~80KB
Pandas
Pandas 是基于 NumPy 构建的数据分析库,提供了高效、灵活的数据结构,让数据处理和分析变得更加简单。
推荐先在官方文档浏览API目录,可以快速这个这个框架的应用范围有个了解。再向下阅读常用的API使用方法。
Pandas 的核心数据结构:
- Series:一维数据结构,带标签的数组(类似于 Excel 的一列)
- DataFrame:二维数据结构,带标签的表格(类似于 Excel 表格或 SQL 表)
Pandas 的核心优势:
- 灵活的数据结构:轻松处理各种类型的数据
- 强大的数据操作:筛选、分组、合并、透视等
- 时间序列支持:内置日期时间处理功能
- 缺失值处理:方便的缺失数据处理方法
- 数据IO:支持 CSV、Excel、SQL、JSON 等格式
数据结构关系:
- Series = 一维数组 + 行索引
- DataFrame = 多个 Series 组成的二维表 + 行索引 + 列索引
- DataFrame 可以看作是 Series 的字典(共用行索引)
# Series:一列数据
s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
# DataFrame:多列数据
df = pd.DataFrame({
'col1': [1, 2, 3],
'col2': [4, 5, 6]
}, index=['a', 'b', 'c'])
数据结构
Series
Series 是带标签的一维数组,可以存储任何数据类型。
import pandas as pd
import numpy as np
# 从列表创建
s = pd.Series([1, 2, 3, 4])
print(s)
# 0 1
# 1 2
# 2 3
# 3 4
# dtype: int64
# 指定索引
s = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
print(s)
# a 1
# b 2
# c 3
# d 4
# dtype: int64
# 从字典创建
data = {'Japan': 'Tokyo', 'Korea': 'Seoul', 'China': 'Beijing'}
s = pd.Series(data)
print(s)
# Japan Tokyo
# Korea Seoul
# China Beijing
# dtype: object
# 从标量创建
s = pd.Series(5, index=['a', 'b', 'c'])
print(s)
# a 5
# b 5
# c 5
# dtype: int64
Series 的属性:
import pandas as pd
s = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
# 查看值
print(s.values) # [1 2 3 4]
# 查看索引
print(s.index) # Index(['a', 'b', 'c', 'd'], dtype='object')
# 查看形状
print(s.shape) # (4,)
# 查看大小
print(s.size) # 4
# 查看数据类型
print(s.dtype) # int64
# 设置名称
s.name = "Numbers"
s.index.name = "Letters"
print(s)
# Letters
# a 1
# b 2
# c 3
# d 4
# Name: Numbers, dtype: int64
Series 的索引:
import pandas as pd
s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd'])
# 通过标签索引
print(s['a']) # 10
print(s[['a', 'c']]) # 多个标签
# 通过位置索引
print(s[0]) # 10
print(s[[0, 2]]) # 多个位置
# 切片
print(s['a':'c']) # 标签切片(包含终点!)
print(s[0:2]) # 位置切片(不包含终点)
# 布尔索引
print(s[s > 20])
# c 30
# d 40
# 条件筛选
print(s[(s > 15) & (s < 35)])
# b 20
# c 30
标签切片 vs 位置切片:
s = pd.Series([10, 20, 30], index=['a', 'b', 'c'])
s['a':'c'] # 包含 'c',结果:a=10, b=20, c=30
s[0:2] # 不包含索引2,结果:a=10, b=20
Series 的运算:
import pandas as pd
s = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
# 向量化运算
print(s + 10) # 每个元素加10
print(s * 2) # 每个元素乘2
print(s ** 2) # 每个元素平方
# Series 之间的运算
s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([4, 5, 6], index=['b', 'c', 'd'])
print(s1 + s2)
# a NaN # 只在 s1 中
# b 6.0 # 2 + 4
# c 8.0 # 3 + 5
# d NaN # 只在 s2 中
# 通用函数
print(np.sqrt(s)) # 平方根
print(np.exp(s)) # 指数
# 统计函数
print(s.sum()) # 10
print(s.mean()) # 2.5
print(s.max()) # 4
print(s.min()) # 1
print(s.std()) # 标准差
print(s.describe()) # 统计描述
DataFrame
DataFrame 是二维表格数据结构,有行索引和列索引。
import pandas as pd
# 从字典创建(推荐)
data = {
'name': ['Alice', 'Bob', 'Charlie'],
'age': [25, 30, 35],
'city': ['Beijing', 'Shanghai', 'Guangzhou']
}
df = pd.DataFrame(data)
print(df)
# name age city
# 0 Alice 25 Beijing
# 1 Bob 30 Shanghai
# 2 Charlie 35 Guangzhou
# 指定索引
df = pd.DataFrame(data, index=['row1', 'row2', 'row3'])
print(df)
# 指定列顺序
df = pd.DataFrame(data, columns=['city', 'name', 'age'])
print(df)
# 从列表的列表创建
data = [
['Alice', 25, 'Beijing'],
['Bob', 30, 'Shanghai'],
['Charlie', 35, 'Guangzhou']
]
df = pd.DataFrame(data, columns=['name', 'age', 'city'])
print(df)
# 从 NumPy 数组创建
data = np.array([['Japan', 'Tokyo', 4000],
['Korea', 'Seoul', 1300],
['China', 'Beijing', 9100]])
df = pd.DataFrame(data, columns=['nation', 'capital', 'GDP'])
print(df)
# 从 Series 字典创建
s1 = pd.Series([1, 2, 3])
s2 = pd.Series([4, 5, 6])
df = pd.DataFrame({'col1': s1, 'col2': s2})
print(df)
DataFrame 的属性:
import pandas as pd
df = pd.DataFrame({
'A': [1, 2, 3],
'B': [4, 5, 6],
'C': [7, 8, 9]
})
# 查看形状
print(df.shape) # (3, 3) - 3行3列
# 查看大小
print(df.size) # 9 - 总元素数
# 查看维度
print(df.ndim) # 2
# 查看列名
print(df.columns) # Index(['A', 'B', 'C'], dtype='object')
# 查看行索引
print(df.index) # RangeIndex(start=0, stop=3, step=1)
# 查看数据类型
print(df.dtypes)
# A int64
# B int64
# C int64
# 查看值(NumPy 数组)
print(df.values)
# [[1 4 7]
# [2 5 8]
# [3 6 9]]
# 查看信息
print(df.info())
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 3 entries, 0 to 2
# Data columns (total 3 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 A 3 non-null int64
# 1 B 3 non-null int64
# 2 C 3 non-null int64
# 统计描述
print(df.describe())
# A B C
# count 3.000000 3.000000 3.000000
# mean 2.000000 5.000000 8.000000
# std 1.000000 1.000000 1.000000
# min 1.000000 4.000000 7.000000
# 25% 1.500000 4.500000 7.500000
# 50% 2.000000 5.000000 8.000000
# 75% 2.500000 5.500000 8.500000
# max 3.000000 6.000000 9.000000
查看数据:
import pandas as pd
df = pd.DataFrame({
'A': range(10),
'B': range(10, 20),
'C': range(20, 30)
})
# 查看前几行
print(df.head()) # 默认前5行
print(df.head(3)) # 前3行
# 查看后几行
print(df.tail()) # 默认后5行
print(df.tail(3)) # 后3行
# 随机抽样
print(df.sample(3)) # 随机3行
print(df.sample(frac=0.5)) # 随机50%
数据初探必备三件套:
df.head() # 看前几行,了解数据结构
df.info() # 看列信息、类型、缺失值
df.describe() # 看统计摘要
Index
Index 是 Pandas 的行索引或列索引对象。
import pandas as pd
# 创建普通索引
index = pd.Index(['a', 'b', 'c', 'd'])
print(index)
# Index(['a', 'b', 'c', 'd'], dtype='object')
# 数值索引
index = pd.Index([1, 2, 3, 4])
print(index)
# Index([1, 2, 3, 4], dtype='int64')
# Index 是不可变的
# index[0] = 'x' # TypeError
# Index 操作
index1 = pd.Index(['A', 'B', 'C'])
index2 = pd.Index(['C', 'D', 'E'])
print(index1.union(index2)) # 并集
print(index1.intersection(index2)) # 交集
print(index1.difference(index2)) # 差集
MultiIndex(多重索引):
import pandas as pd
# 从元组列表创建
index = pd.MultiIndex.from_tuples([
('A', 1), ('A', 2), ('B', 1), ('B', 2)
])
print(index)
# 从数组创建
arrays = [
['A', 'A', 'B', 'B'],
[1, 2, 1, 2]
]
index = pd.MultiIndex.from_arrays(arrays, names=['letter', 'number'])
print(index)
# 在 DataFrame 中使用
df = pd.DataFrame({
'value': [10, 20, 30, 40]
}, index=index)
print(df)
# value
# letter number
# A 1 10
# 2 20
# B 1 30
# 2 40
# 访问多重索引
print(df.loc['A'])
# value
# number
# 1 10
# 2 20
print(df.loc[('A', 1)])
# value 10
DatetimeIndex(时间索引):
import pandas as pd
# 从日期字符串创建
dates = pd.to_datetime(['2021-01-01', '2021-01-02', '2021-01-03'])
print(dates)
# DatetimeIndex(['2021-01-01', '2021-01-02', '2021-01-03'])
# 生成日期范围
dates = pd.date_range('2021-01-01', periods=10, freq='D')
print(dates)
# DatetimeIndex(['2021-01-01', '2021-01-02', ..., '2021-01-10'])
# 不同频率
dates_monthly = pd.date_range('2021-01', periods=12, freq='M')
dates_hourly = pd.date_range('2021-01-01', periods=24, freq='H')
dates_business = pd.date_range('2021-01-01', periods=10, freq='B') # 工作日
# 在 DataFrame 中使用
df = pd.DataFrame({
'value': range(10)
}, index=dates)
print(df.head())
# 时间索引的优势
print(df['2021-01-01']) # 按日期选择
print(df['2021-01-01':'2021-01-03']) # 日期范围
print(df['2021-01']) # 按月选择
时间频率代码:
D:日W:周M:月末MS:月初Q:季度末Y:年末H:小时T或min:分钟S:秒B:工作日
数据IO
Pandas 支持多种数据格式的读写,让数据交换变得非常便捷。
CSV/Excel 文件
import pandas as pd
# 创建示例数据
data = [['Google', 10], ['Runoob', 12], ['Wiki', 13]]
df = pd.DataFrame(data, columns=['Site', 'Age'], dtype=float)
# CSV 文件读写
df.to_csv('file1.csv', index=False) # 写入 CSV
df = pd.read_csv('file1.csv') # 读取 CSV
# Excel 文件读写(.xlsx 格式)
df.to_excel('file1.xlsx', index=False) # 写入 Excel
df = pd.read_excel('file1.xlsx') # 读取 Excel
# Excel 文件读写(.xls 格式)
df.to_excel('file1.xls', index=False)
df = pd.read_excel('file1.xls')
print(df)
# Site Age
# 0 Google 10.0
# 1 Runoob 12.0
# 2 Wiki 13.0
常用参数:
index=False:不保存行索引header=None:没有列名时使用encoding='utf-8':指定编码格式sep=',':指定分隔符(CSV)sheet_name='Sheet1':指定 Excel 工作表
# 读取 CSV 时的常用参数
df = pd.read_csv('data.csv',
encoding='utf-8', # 编码
sep=',', # 分隔符
header=0, # 第一行为列名
index_col=0, # 第一列为索引
na_values=['NA', 'null'], # 缺失值标记
skiprows=1, # 跳过第一行
nrows=100) # 只读100行
# 读取 Excel 时的常用参数
df = pd.read_excel('data.xlsx',
sheet_name='Sheet1', # 工作表名
header=0, # 第一行为列名
usecols='A:C') # 只读A到C列
JSON 文件
import pandas as pd
# 准备 JSON 数据
data = [
{
"id": "A001",
"name": "菜鸟教程",
"url": "www.runoob.com",
"likes": 61
},
{
"id": "A002",
"name": "Google",
"url": "www.google.com",
"likes": 124
},
{
"id": "A003",
"name": "淘宝",
"url": "www.taobao.com",
"likes": 45
}
]
# 创建 DataFrame
df = pd.DataFrame(data)
# JSON 文件读写
df.to_json('sites.json') # 写入 JSON
df = pd.read_json('sites.json') # 读取 JSON
print(df.to_string())
# id name url likes
# 0 A001 菜鸟教程 www.runoob.com 61
# 1 A002 Google www.google.com 124
# 2 A003 淘宝 www.taobao.com 45
JSON 格式选项:
# 不同的 JSON 方向(orient 参数)
df.to_json('data.json', orient='records') # 记录列表格式
df.to_json('data.json', orient='index') # 以索引为键
df.to_json('data.json', orient='columns') # 以列名为键(默认)
df.to_json('data.json', orient='values') # 只保存值
SQL 数据库
from sqlalchemy import create_engine, text
import pandas as pd
# 数据库连接配置
MYSQL_HOST = 'localhost'
MYSQL_PORT = 3306
MYSQL_USER = 'employee_u'
MYSQL_PASSWORD = 'employee_s'
MYSQL_DB = 'employee'
# 创建数据库引擎
engine = create_engine(
f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}',
echo=False
)
# 准备数据
dataset = pd.DataFrame({
'Names': ['Abhinav', 'Aryan', 'Manthan'],
'DOB': ['10/01/2009', '24/03/2009', '28/02/2009']
})
# 写入数据库
dataset.to_sql('Employee_Data', con=engine, index=False, if_exists='replace')
# 读取数据的多种方式
with engine.connect() as conn:
# 方式1:读取整个表
df1 = pd.read_sql('Employee_Data', con=conn)
# 方式2:使用 SQL 查询
df2 = pd.read_sql_query(text("SELECT * FROM Employee_Data"), con=conn)
# 方式3:读取表(专用方法)
df3 = pd.read_sql_table("Employee_Data", conn)
print(df2)
if_exists 参数控制表存在时的行为:
'fail':如果表存在,报错(默认)'replace':删除原表,创建新表'append':在现有表中追加数据
# 替换表
df.to_sql('table_name', con=engine, if_exists='replace')
# 追加数据
df.to_sql('table_name', con=engine, if_exists='append')
# 分批写入大量数据
df.to_sql('table_name', con=engine, chunksize=1000)
数据库操作注意事项:
- 确保安装了相应的数据库驱动(如
pymysql、psycopg2) - 大量数据写入时,考虑使用
chunksize参数分批写入 - 使用参数化查询防止 SQL 注入
- 记得关闭数据库连接释放资源
数据选择
Pandas 提供了多种方式来选择和筛选数据。
基本选择
import pandas as pd
df = pd.DataFrame({
'A': [1, 2, 3, 4],
'B': [5, 6, 7, 8],
'C': [9, 10, 11, 12]
}, index=['row1', 'row2', 'row3', 'row4'])
print(df)
# A B C
# row1 1 5 9
# row2 2 6 10
# row3 3 7 11
# row4 4 8 12
# 选择列
print(df['A']) # 单列,返回 Series
print(df[['A', 'C']]) # 多列,返回 DataFrame
# 选择行(切片)
print(df[0:2]) # 前两行
print(df['row1':'row3']) # 标签切片(包含终点)
# 列操作
df['D'] = df['A'] + df['B'] # 新增列
df['E'] = 100 # 常量列
del df['E'] # 删除列
列选择的两种方式:
df['A'] # 推荐:总是有效,支持特殊字符
df.A # 便捷:但列名不能与方法重名
# 例如:
df.count # 这是方法,不是列
df['count'] # 这才是选择名为'count'的列
loc 和 iloc
import pandas as pd
df = pd.DataFrame({
'A': [1, 2, 3, 4],
'B': [5, 6, 7, 8],
'C': [9, 10, 11, 12]
}, index=['row1', 'row2', 'row3', 'row4'])
# loc:基于标签的索引
print(df.loc['row1']) # 单行
print(df.loc[['row1', 'row3']]) # 多行
print(df.loc['row1', 'A']) # 单个元素
print(df.loc['row1':'row3', 'A':'B']) # 行列切片
# iloc:基于位置的索引
print(df.iloc[0]) # 第一行
print(df.iloc[[0, 2]]) # 第1和第3行
print(df.iloc[0, 1]) # 第1行第2列
print(df.iloc[0:2, 0:2]) # 行列位置切片
# 布尔索引
print(df.loc[df['A'] > 2]) # 筛选行
print(df.loc[df['A'] > 2, ['A', 'C']]) # 筛选行和列
# 赋值
df.loc['row1', 'A'] = 100
df.iloc[0, 0] = 200
loc vs iloc vs []:
| 方式 | 索引类型 | 切片行为 | 用途 |
|---|---|---|---|
[] | 列名/行位置 | 不包含终点 | 简单列选择、行切片 |
.loc[] | 标签 | 包含终点 | 灵活的标签索引 |
.iloc[] | 位置 | 不包含终点 | 位置索引 |
# 选择列
df['A'] # 简单
df.loc[:, 'A'] # 明确
# 选择行
df[0:2] # 位置切片
df.loc['a':'c'] # 标签切片
df.iloc[0:2] # 位置切片
# 行列组合
df.loc['a', 'A'] # 标签
df.iloc[0, 0] # 位置
条件筛选
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David'],
'age': [25, 30, 35, 28],
'city': ['Beijing', 'Shanghai', 'Beijing', 'Guangzhou'],
'salary': [5000, 6000, 7000, 5500]
})
# 单条件筛选
print(df[df['age'] > 28])
# 多条件筛选(与)
print(df[(df['age'] > 25) & (df['salary'] > 5500)])
# 多条件筛选(或)
print(df[(df['city'] == 'Beijing') | (df['salary'] > 6000)])
# 使用 isin
print(df[df['city'].isin(['Beijing', 'Shanghai'])])
# 字符串包含
print(df[df['name'].str.contains('a')])
# 使用 query 方法(更易读)
print(df.query('age > 28 and salary > 5500'))
print(df.query('city == "Beijing" or salary > 6000'))
# 筛选后选择列
print(df.loc[df['age'] > 28, ['name', 'city']])
条件筛选技巧:
# 使用 & | ~ 而不是 and or not
df[(df['age'] > 25) & (df['salary'] > 5000)] # 正确
# df[(df['age'] > 25) and (df['salary'] > 5000)] # 错误
# 条件需要加括号
df[(df['age'] > 25) & (df['salary'] > 5000)] # 正确
# df[df['age'] > 25 & df['salary'] > 5000] # 错误
# query 方法更清晰
df.query('age > 25 and salary > 5000') # 可以用 and/or
数据清洗
缺失值处理
import pandas as pd
import numpy as np
df = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, np.nan, 8],
'C': [9, 10, 11, 12]
})
print(df)
# A B C
# 0 1.0 5.0 9
# 1 2.0 NaN 10
# 2 NaN NaN 11
# 3 4.0 8.0 12
# 检测缺失值
print(df.isna()) # 或 df.isnull()
print(df.isna().sum()) # 每列缺失值数量
# 检测非缺失值
print(df.notna()) # 或 df.notnull()
# 删除缺失值
print(df.dropna()) # 删除含有缺失值的行
print(df.dropna(axis=1)) # 删除含有缺失值的列
print(df.dropna(how='all')) # 删除全为缺失值的行
print(df.dropna(thresh=2)) # 保留至少有2个非缺失值的行
# 填充缺失值
print(df.fillna(0)) # 填充为0
print(df.fillna({'A': 0, 'B': 99})) # 不同列填充不同值
print(df.fillna(method='ffill')) # 前向填充
print(df.fillna(method='bfill')) # 后向填充
print(df.fillna(df.mean())) # 用均值填充
# 插值
print(df.interpolate()) # 线性插值
缺失值处理策略:
删除:
- 缺失比例小(
<5%) - 缺失是完全随机的
- 数据量足够大
填充:
- 常数:0、-1、均值、中位数
- 前后值:ffill、bfill
- 插值:线性、多项式
# 统计缺失值
missing = df.isna().sum()
missing_pct = df.isna().sum() / len(df) * 100
print(pd.DataFrame({'count': missing, 'percentage': missing_pct}))
# 只保留完整数据的列
df_clean = df.dropna(axis=1)
# 组合策略
df_filled = df.copy()
df_filled['A'].fillna(df_filled['A'].mean(), inplace=True)
df_filled['B'].fillna(method='ffill', inplace=True)
重复值处理
import pandas as pd
df = pd.DataFrame({
'A': [1, 1, 2, 3, 3],
'B': [5, 5, 6, 7, 7],
'C': [9, 9, 10, 11, 11]
})
print(df)
# A B C
# 0 1 5 9
# 1 1 5 9
# 2 2 6 10
# 3 3 7 11
# 4 3 7 11
# 检测重复行
print(df.duplicated())
# 0 False
# 1 True
# 2 False
# 3 False
# 4 True
# 统计重复
print(df.duplicated().sum()) # 2
# 删除重复行
print(df.drop_duplicates()) # 保留第一次出现
print(df.drop_duplicates(keep='last')) # 保留最后一次出现
print(df.drop_duplicates(keep=False)) # 删除所有重复
# 基于特定列去重
print(df.drop_duplicates(subset=['A']))
print(df.drop_duplicates(subset=['A', 'B']))
# 原地修改
df.drop_duplicates(inplace=True)
数据类型转换
import pandas as pd
df = pd.DataFrame({
'A': ['1', '2', '3'],
'B': [1.0, 2.0, 3.0],
'C': ['2021-01-01', '2021-01-02', '2021-01-03']
})
print(df.dtypes)
# A object
# B float64
# C object
# 转换单列
df['A'] = df['A'].astype(int)
df['B'] = df['B'].astype(int)
df['C'] = pd.to_datetime(df['C'])
print(df.dtypes)
# A int64
# B int64
# C datetime64[ns]
# 批量转换
df = df.astype({'A': 'int32', 'B': 'float32'})
# 尝试转换(失败时不报错)
df['A'] = pd.to_numeric(df['A'], errors='coerce') # 转换失败变NaN
df['A'] = pd.to_numeric(df['A'], errors='ignore') # 转换失败保持原样
# 分类类型(节省内存)
df['category'] = pd.Categorical(['A', 'B', 'A', 'C'])
常用类型转换:
# 数值类型
pd.to_numeric(df['col']) # 转数值
pd.to_numeric(df['col'], errors='coerce') # 失败变NaN
# 时间类型
pd.to_datetime(df['col']) # 转日期
pd.to_datetime(df['col'], format='%Y-%m-%d') # 指定格式
# 字符串类型
df['col'].astype(str) # 转字符串
# 分类类型(适合重复值多的列)
df['col'] = df['col'].astype('category')
数 据转换
apply/map/applymap
import pandas as pd
import numpy as np
df = pd.DataFrame({
'name': ['alice', 'bob', 'charlie'],
'age': [25, 30, 35],
'salary': [5000, 6000, 7000]
})
# map:Series 级别的映射
df['name_upper'] = df['name'].map(str.upper)
df['age_group'] = df['age'].map(lambda x: '年轻' if x < 30 else '成熟')
# 使用字典映射
name_mapping = {'alice': 'Alice', 'bob': 'Bob', 'charlie': 'Charlie'}
df['name_title'] = df['name'].map(name_mapping)
print(df)
# apply:DataFrame/Series 级别的应用
# 对列操作(axis=0,默认)
print(df[['age', 'salary']].apply(np.mean))
# 对行操作(axis=1)
df['total'] = df.apply(lambda row: row['age'] + row['salary'], axis=1)
# 返回多个值
def analyze(series):
return pd.Series({
'min': series.min(),
'max': series.max(),
'mean': series.mean()
})
print(df[['age', 'salary']].apply(analyze))
# applymap:DataFrame 所有元素的映射
df_num = df[['age', 'salary']]
df_doubled = df_num.applymap(lambda x: x * 2)
print(df_doubled)
map vs apply vs applymap:
| 方法 | 应用对象 | 作用 | 返回 |
|---|---|---|---|
map | Series | 元素级映射 | Series |
apply | Series/DataFrame | 函数应用 | Series/DataFrame |
applymap | DataFrame | 所有元素映射 | DataFrame |
# map:一对一映射(最快)
df['col'].map({'A': 1, 'B': 2})
df['col'].map(lambda x: x * 2)
# apply:灵活函数应用
df['col'].apply(complex_function)
df.apply(lambda row: row['A'] + row['B'], axis=1)
# applymap:所有元素(已弃用,建议用map)
df.applymap(lambda x: x * 2)
df.map(lambda x: x * 2) # 新版推荐
排序
import pandas as pd
df = pd.DataFrame({
'name': ['Charlie', 'Alice', 'Bob'],
'age': [35, 25, 30],
'salary': [7000, 5000, 6000]
})
# 按值排序
print(df.sort_values('age')) # 按age升序
print(df.sort_values('age', ascending=False)) # 降序
# 多列排序
print(df.sort_values(['age', 'salary']))
print(df.sort_values(['age', 'salary'], ascending=[True, False]))
# 按索引排序
df_indexed = df.set_index('name')
print(df_indexed.sort_index()) # 按行索引排序
print(df_indexed.sort_index(axis=1)) # 按列名排序
# 原地排序
df.sort_values('age', inplace=True)
# rank:计算排名
df['age_rank'] = df['age'].rank()
df['salary_rank'] = df['salary'].rank(ascending=False)
df['age_rank_dense'] = df['age'].rank(method='dense')
print(df)
rank 方法的 method 参数:
average:相同值取平均排名(默认)min:相同值取最小排名max:相同值取最大排名first:按出现顺序排名dense:紧密排名,无跳号
s = pd.Series([1, 3, 3, 5])
s.rank(method='average') # [1.0, 2.5, 2.5, 4.0]
s.rank(method='min') # [1.0, 2.0, 2.0, 4.0]
s.rank(method='max') # [1.0, 3.0, 3.0, 4.0]
s.rank(method='first') # [1.0, 2.0, 3.0, 4.0]
s.rank(method='dense') # [1.0, 2.0, 2.0, 3.0]
数据合并
import pandas as pd
df1 = pd.DataFrame({
'A': [1, 2, 3],
'B': [4, 5, 6]
})
df2 = pd.DataFrame({
'A': [7, 8, 9],
'B': [10, 11, 12]
})
# concat:垂直/水平拼接
print(pd.concat([df1, df2])) # 垂直拼接(默认axis=0)
print(pd.concat([df1, df2], axis=1)) # 水平拼接
print(pd.concat([df1, df2], ignore_index=True)) # 重置索引
# merge:类SQL的合并
left = pd.DataFrame({
'key': ['A', 'B', 'C'],
'value1': [1, 2, 3]
})
right = pd.DataFrame({
'key': ['A', 'B', 'D'],
'value2': [4, 5, 6]
})
# 内连接(默认)
print(pd.merge(left, right, on='key'))
# key value1 value2
# 0 A 1 4
# 1 B 2 5
# 左连接
print(pd.merge(left, right, on='key', how='left'))
# key value1 value2
# 0 A 1 4.0
# 1 B 2 5.0
# 2 C 3 NaN
# 右连接
print(pd.merge(left, right, on='key', how='right'))
# 外连接
print(pd.merge(left, right, on='key', how='outer'))
# key value1 value2
# 0 A 1.0 4.0
# 1 B 2.0 5.0
# 2 C 3.0 NaN
# 3 D NaN 6.0
# 不同列名连接
left = pd.DataFrame({'key_left': ['A', 'B'], 'value': [1, 2]})
right = pd.DataFrame({'key_right': ['A', 'B'], 'value': [3, 4]})
print(pd.merge(left, right, left_on='key_left', right_on='key_right'))
# join:基于索引的合并
df1 = pd.DataFrame({'A': [1, 2]}, index=['a', 'b'])
df2 = pd.DataFrame({'B': [3, 4]}, index=['a', 'b'])
print(df1.join(df2))
merge 的 how 参数:
| 类型 | SQL等价 | 说明 |
|---|---|---|
inner | INNER JOIN | 保留两边都有的键 |
left | LEFT JOIN | 保留左边所有键 |
right | RIGHT JOIN | 保留右边所有键 |
outer | FULL OUTER JOIN | 保留所有键 |
cross | CROSS JOIN | 笛卡尔积 |
# 多列连接
pd.merge(df1, df2, on=['key1', 'key2'])
# 解决重复列名
pd.merge(df1, df2, on='key', suffixes=('_left', '_right'))
# 指示器:显示数据来源
pd.merge(df1, df2, on='key', how='outer', indicator=True)
数据分析
分组聚合(GroupBy)
import pandas as pd
import numpy as np
df = pd.DataFrame({
'class': ['A', 'A', 'B', 'B', 'C', 'C'],
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'],
'score': [85, 90, 75, 80, 95, 88]
})
# 基本分组
grouped = df.groupby('class')
# 聚合函数
print(grouped['score'].mean()) # 每组平均分
print(grouped['score'].sum()) # 每组总分
print(grouped['score'].count()) # 每组人数
print(grouped['score'].max()) # 每组最高分
# 多个聚合函数
print(grouped['score'].agg(['mean', 'sum', 'count', 'max', 'min']))
# 不同列不同聚合
print(grouped.agg({
'score': ['mean', 'max'],
'name': 'count'
}))
# 自定义聚合函数
print(grouped['score'].agg(lambda x: x.max() - x.min()))
# 多列分组
df['semester'] = ['S1', 'S1', 'S1', 'S2', 'S2', 'S2']
print(df.groupby(['class', 'semester'])['score'].mean())
# 遍历分组
for name, group in grouped:
print(f"\nClass {name}:")
print(group)
# transform:保持原DataFrame形状
df['score_mean'] = grouped['score'].transform('mean')
df['score_normalized'] = grouped['score'].transform(lambda x: (x - x.mean()) / x.std())
print(df)
# filter:筛选分组
print(grouped.filter(lambda x: x['score'].mean() > 85))
GroupBy 三部曲:Split-Apply-Combine
- Split:按条件分组
- Apply:对每组应用函数
- Combine:合并结果
# 常用模式
df.groupby('key').agg('mean') # 聚合
df.groupby('key').transform('mean') # 转换(保持形状)
df.groupby('key').filter(lambda x: len(x) > 2) # 筛选分组
df.groupby('key').apply(custom_function) # 自定义函数
数据透视(Pivot)
import pandas as pd
df = pd.DataFrame({
'date': ['2021-01', '2021-01', '2021-02', '2021-02'],
'city': ['Beijing', 'Shanghai', 'Beijing', 'Shanghai'],
'sales': [100, 150, 120, 160],
'profit': [20, 30, 25, 35]
})
# pivot_table:创建透视表
pivot = pd.pivot_table(
df,
values='sales', # 要聚合的值
index='date', # 行索引
columns='city', # 列索引
aggfunc='sum' # 聚合函数
)
print(pivot)
# city Beijing Shanghai
# date
# 2021-01 100 150
# 2021-02 120 160
# 多个聚合函数
pivot = pd.pivot_table(
df,
values='sales',
index='date',
columns='city',
aggfunc=['sum', 'mean']
)
print(pivot)
# 多个值列
pivot = pd.pivot_table(
df,
values=['sales', 'profit'],
index='date',
columns='city',
aggfunc='sum'
)
print(pivot)
# 添加小计
pivot = pd.pivot_table(
df,
values='sales',
index='date',
columns='city',
aggfunc='sum',
margins=True,
margins_name='Total'
)
print(pivot)
# stack/unstack:重塑数据
df_indexed = df.set_index(['date', 'city'])
print(df_indexed)
# unstack:将行索引转为列索引
print(df_indexed.unstack())
# stack:将列索引转为行索引
print(df_indexed.unstack().stack())
Pivot vs Pivot_table:
pivot:不聚合,要求索引/列组合唯一pivot_table:可聚合,处理重复值
# pivot:简单重塑
df.pivot(index='date', columns='city', values='sales')
# pivot_table:需要聚合
df.pivot_table(
values='sales',
index='date',
columns='city',
aggfunc='sum'
)
数据可视化
一提到图表,大家脑海里浮现的,通常是柱状图、饼图、趋势图等等。这是按照图形等维度对图表进行分类,经常会导致图表的误用。
图表的作用,是帮助我们更好地看懂数据。选择什么图表,需要回答的首要问题是『我有什么数据,需要用图表做什么』,而不是 『图表长成什么样』 。
推荐绘图库:
参考链接: