Skip to main content

数据分析

info

数据分析是从数据中提取有价值信息的过程。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 # 只导入部分函数
tip

推荐使用 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]
tip

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位整数)
tip

常用属性速查:

  • 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]]
warning

切片与引用: 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]
tip

布尔运算符:

  • &:与(and)
  • |:或(or)
  • ~:非(not)

注意要使用 &|,而不是 andor,并且需要用括号!

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的会被"拉伸"以匹配另一个
tip

广播规则简记:

  1. 如果两个数组形状在某个维度上一致或其中一个为1,则兼容
  2. 维度数较小的数组会在前面补1
  3. 输出形状是每个维度的最大值
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]
tip

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)
warning

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]]
tip

数组合并方法选择:

  • 垂直拼接(增加行):vstackconcatenate(axis=0)
  • 水平拼接(增加列):hstackconcatenate(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 不受影响
warning

三种复制方式的区别:

方式内存数据修改形状修改
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]
tip

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) 的随机排列
tip

新版随机数生成(推荐):

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]]
tip

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")
tip

文件格式选择:

  • 文本文件 (.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 构建的数据分析库,提供了高效、灵活的数据结构,让数据处理和分析变得更加简单。

Pandas 的核心数据结构:

  • Series:一维数据结构,带标签的数组(类似于 Excel 的一列)
  • DataFrame:二维数据结构,带标签的表格(类似于 Excel 表格或 SQL 表)

Pandas 的核心优势:

  • 灵活的数据结构:轻松处理各种类型的数据
  • 强大的数据操作:筛选、分组、合并、透视等
  • 时间序列支持:内置日期时间处理功能
  • 缺失值处理:方便的缺失数据处理方法
  • 数据IO:支持 CSV、Excel、SQL、JSON 等格式
tip

数据结构关系:

  • 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'])

Pandas 官方文档

数据结构

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
warning

标签切片 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%
tip

数据初探必备三件套:

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']) # 按月选择
tip

时间频率代码:

  • D:日
  • W:周
  • M:月末
  • MS:月初
  • Q:季度末
  • Y:年末
  • H:小时
  • Tmin:分钟
  • 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
tip

常用参数:

  • 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
tip

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)
tip

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)
warning

数据库操作注意事项:

  1. 确保安装了相应的数据库驱动(如 pymysqlpsycopg2
  2. 大量数据写入时,考虑使用 chunksize 参数分批写入
  3. 使用参数化查询防止 SQL 注入
  4. 记得关闭数据库连接释放资源

数据选择

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'] # 删除列
tip

列选择的两种方式:

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
tip

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']])
tip

条件筛选技巧:

# 使用 & | ~ 而不是 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()) # 线性插值
tip

缺失值处理策略:

删除:

  • 缺失比例小(<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'])
tip

常用类型转换:

# 数值类型
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)
tip

map vs apply vs applymap:

方法应用对象作用返回
mapSeries元素级映射Series
applySeries/DataFrame函数应用Series/DataFrame
applymapDataFrame所有元素映射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)
tip

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))
tip

merge 的 how 参数:

类型SQL等价说明
innerINNER JOIN保留两边都有的键
leftLEFT JOIN保留左边所有键
rightRIGHT JOIN保留右边所有键
outerFULL OUTER JOIN保留所有键
crossCROSS 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))
tip

GroupBy 三部曲:Split-Apply-Combine

  1. Split:按条件分组
  2. Apply:对每组应用函数
  3. 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())
tip

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'
)

数据可视化

一提到图表,大家脑海里浮现的,通常是柱状图、饼图、趋势图等等。这是按照图形等维度对图表进行分类,经常会导致图表的误用。

图表的作用,是帮助我们更好地看懂数据。选择什么图表,需要回答的首要问题是『我有什么数据,需要用图表做什么』,而不是 『图表长成什么样』 。

推荐绘图库:

参考链接: