跳转至

第8章 数据处理与分析基础

8.1 文件读写

8.1.1 文件的打开和关闭

  • 为什么要打开和关闭文件?
  • 打开文件是指将访问文件所需要的属性 信息从外存读取至内存之中,每次文件访问都会利用这些信息来对文件进行读、写操作, 从而避免频繁的文件检索,提升文件访问的效率
  • 文件访问任务完成之后,需要关闭文 件以释放这些文件信息,同时将文件属性信息的变化写入磁盘文件之中

  • open函数

  • 开文件并返回一个文件对象

  • open函数的参数

  • file: 取值为一个类路径对象(path-like object)
  • mode:模式字符串,用于指定文件的打开模式

  • 文件的打开模式

模式字符 含义
'r' 读取模式(默认)
'w' 写入模式,如果文件存在则将其覆盖
'a' 写入模式,如果文件存在则将写入内容追加至尾部 'x' 排它性创建模式,如果文件已存在则打开失败 'b' 二进制模式
't' 文本模式(默认)
'+' 更新模式(可读可写)
  • 组合打开模式
  • 'rb':以二进制文件读取模式打开
  • 'wb':以二进制文件写 入模式打开
  • 'a+':以文本文件读取和追加模式打开
  • 'ab+':以二进制文件 读取和追加模式打开
  • 'w+':以文本文件读写模式打开

  • 文件访问的过程

1
2
3
4
>>> f = open('/path/to/file', 'rt') ...
# 文件读写操作
...
>>> f.close()

8.1.2 路径管理

  • os模块和os.path子模块
函数 功能
os.listdir(path) 返回 path 文件夹中的文件名构成的列表
os.path.exists(path) 路径 path 是否存在
os.path.isabs(path) path 是否为绝对路径
os.path.isdir(path) path 是否为目录
os.path.isfile(path) path 是否为文件
os.path.abspath(path) 获取 path 的绝对路径
os.path.basename(path) 获取 path 的最后一项(文件名或最底层文件夹)
os.path.dirname(path) 获取 pathbasename 的所在路径
os.path.split(path) path 切分为 dirnamebasename 两部分
os.path.getsize(path) 获取文件的大小(字节)
os.path.getatime(path) 获取文件或路径的最后访问时间(秒)
os.path.getmtime(path) 获取文件或路径的最后修改时间(秒)
os.path.join(path1, path2, ...) 将多个路径片段拼接为一个合法的路径
  • 首先需要在计算机 桌面上创建文件'file.txt',然后打开命令行终端并进入用户主目录,进入Python环境并执行如下命令
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
>>> from os.path import *
>>> path = join('./Desktop/', 'file.txt')
>>> abs_path = abspath(path)
>>> abs_path
'/Users/username/Desktop/file.txt'
>>> isabs(abs_path)
True
>>> isfile(abs_path)
True
>>> isdir(abs_path)
False
>>> basename(abs_path)
'file.txt'
>>> dirname(abs_path)
'/Users/username/Desktop'
>>> split(abs_path)
('/Users/username/Desktop', 'file.txt')
  • pathlib模块
  • 对路径管理功能进行了封装,该模块的关键组成部分是三个类Path、 WindowsPath和PosixPath,分别用于处理 Windows 风格 的路径及 Posix(Linux、macOS 等)风格的路径
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
>>> from pathlib import Path
>>> path = Path('./Desktop')           # 创建Path对象
>>> path = path / 'file.txt'           # 路径拼接
>>> path
PosixPath('Desktop/file.txt')
>>> path = path.absolute()             # 获取绝对路径
>>> path
PosixPath('/Users/username/Desktop/file.txt')
>>> path.exists()                      # 判断文件或文件夹是否存在
True
>>> path.is_file()                     # 判断指定路径是否是文件
True
>>> path.is_dir()                      # 判断指定路径是否是文件夹
False
>>> path.name                          # 获取文件名
'file.txt'
>>> path.parent
PosixPath('/Users/username/Desktop')
>>> path_iter = path.parent.iterdir()  # 获取父目录的迭代器
>>> list(path_iter)                    # 获取父目录中的文件列表
[PosixPath('/Users/username/Desktop/file.txt'), ... ]

8.1.3 文本文件读写

常用文本文件读写方法

  • read():读入文本文件全部内容作为字符串返回;
  • readline():读入文本文件中的一行并将文件指针的位置后移一行;
  • readlines():读入文本文件全部内容,返回一个字符串列表,列表中的每个元素是文本文件的一行内容;
  • write(text_str):将text_str表示的字符串写入文本文件;
  • writelines(str_iter):将可迭代对象str_iter写入文本文件,str_iter中的每个字符串作为文件的一行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
>>> f = open('./file.txt', 'w')               # 以写入模式打开文件
>>> f.write('line1\nline2\nline3\n')          # 写入第1到3行内容
18
>>> f.writelines(['line4\n', 'line5\n'])      # 写入第4、5行内容
>>> f.close()                                 # 关闭文件
>>> f = open('./file.txt')                    # 以读取模式打开文件
>>> f.read()                                  # 读取文件全部内容
'line1\nline2\nline3\nline4\nline5\n'
>>> f.seek(0)                                 # 移动文件指针至起始位置
0
>>> f.readlines()                             # 按行读取文件全部内容
['line1\n', 'line2\n', 'line3\n', 'line4\n', 'line5\n']
>>> f.close()
>>> f = open('./file.txt', 'a')               # 以追回模式打开文件
>>> f.write('line6\nline7\n')                 # 追加写入第6、7行内容
12
>>> f.close()
>>> f = open('./file.txt')
>>> f.read()
'line1\nline2\nline3\nline4\nline5\nline6\nline7\n'
>>> f.close()

大文件的读取

  • read方法和readlines方法会读入整个文件,在读取较大的文本文件时非常耗时,更严重的是可能会由于内存不足而使得程序无法运行
  • 解决方法1:使用readline方法
1
2
3
4
5
6
7
f = open('/path/to/file.txt')
while True:
    line = f.readline()
    if not line:
        break
    # 对文本行line进行处理
f.close()
  • 解决方法2:直接对文件对象进行迭代
1
2
3
4
5
f = open('/path/to/file.txt')
for line in f:
    pass
    # 对文本行line进行处理
f.close()

二进制文件读写*

  • 使用open函数以'rb''wb'模式打开文件,即可对二进制文件进行读写操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
f = open('/path/to/binfile.bin', 'wb')  # 以二进制写入模式打开文件
f.write('二进制字节串'.encode('utf-8')) # 字符串编码为字节串,并写入文件
f.close()

f = open('/path/to/binfile.bin', 'rb')  # 以二进制读取模式打开文件
content = f.read()
f.close()
print("解码前:", content)
content = content.decode('utf-8')       # 对字节串进行解码
print("解码后:", content)
输出:
1
2
解码前 b'\xe4\xba\x8c\xe8\xbf\x9b\xe5\x88\xb6\xe5\xad\x97\xe8\x8a\x82\xe4\xb8\xb2'
解码后 二进制字节串

8.2 上下文管理

  • 上下文
  • 指程序运行的环境,某些操作只有在相应的环境或上下文之中时才能正确地运行
  • 文件的读写操作必须在文件已经被打开的上下文之中
  • 数据库的访问必 须在成功建立数据库连接的上下文之中
  • 上下文管理
  • 上下文环境构建
  • 操作过程中的异常处理
  • 上下文环境的清理
  • ... ...
  • Python 将 构建和清理上下文环境的过程独离出来,称为上下文管理
  • 上下文管理器
  • 具有上下文管理功能的对象,称为上下文管理器

8.2.1 with语句块

  • with语句块会调用上下文管理器的相关方法来构建上下文环境
  • 文件对象就是一个上下文管理器
  • 利用with可以更加方便、安全地访问文件
1
2
3
4
5
6
7
# 写入文件
with open('/path/to/filename.txt', 'w') as f:
    f.write('file contents')

# 读取文件
with open('/path/to/filename.txt', 'r') as f:
    print(f.read())
  • 利用with读取大文件
1
2
3
4
5
6
with open('/path/to/file.txt') as f: 
    while True:
        line = f.readline()
        if not line:
            break
        # 对文本行line进行处理

8.2.2 上下文管理协议*

  • 上下文管理功能由上下文管理协议约定,上下文管理器就是实现了上下文管理协议的类的实例
  • 上下文管理协议
  • __enter__(self):进入with语句块时调用该方法,返回上下文管理器自身或者相关的对象,若有as子句则返回对象会被赋值给as子句中的变量
  • __exit__(self, exc_type, exc_val, exc_tb):离开with语句块时调用该方法
    • 如果with语句块中的代码在执行过程中抛出异常,则异常类型、异常值,以及异常追踪信息分别被传递至三个参数exc_typeexc_valexc_tb
    • 如果没有异常发生, 则传入的三个值都是None
    • 该方法的返回值为一个布尔值,True表示不再向上抛出 捕获的异常,False表示继续抛出异常。

自定义上下文管理器

  • 不再抛出with语句块中的异常
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class File:
    def __init__(self, path, mode):
        self.path = path
        self.mode = mode

    def __enter__(self):
        print("进入上下文环境...")
        self.file = open(self.path, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("离开上下文环境...")
        self.file.close()
        print('异常类型:', exc_type)
        print('异常值:', exc_val)
        print('异常跟踪:', exc_tb)
        return True               # 不再抛出with语句块中的异常
1
2
3
with File('test_file', 'w') as f:
    f.write('file contents')
    raise Exception('程序运行发生异常')
1
2
3
4
5
进入上下文环境...
离开上下文环境...
异常类型: <class 'Exception'>
异常值: 程序运行发生异常
异常跟踪: <traceback object at 0x7f7f04191d40>
  • 抛出with语句块中的异常
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class File:
    def __init__(self, path, mode):
        self.path = path
        self.mode = mode

    def __enter__(self):
        print("进入上下文环境...")
        self.file = open(self.path, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("离开上下文环境...")
        self.file.close()
        print('异常类型:', exc_type)
        print('异常值:', exc_val)
        print('异常跟踪:', exc_tb)
        return False            # 抛出with语句块中的异常
1
2
3
with File('test_file', 'w') as f:
    f.write('file contents')
    raise Exception('程序运行发生异常')
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
进入上下文环境...
离开上下文环境...
异常类型: <class 'Exception'>
异常值: 程序运行发生异常
异常跟踪: <traceback object at 0x7f7f04217800>



---------------------------------------------------------------------------

Exception                                 Traceback (most recent call last)

<ipython-input-4-7a29cbfd732b> in <module>
      1 with File('test_file', 'w') as f:
      2     f.write('file contents')
----> 3     raise Exception('程序运行发生异常')


Exception: 程序运行发生异常

应用

  • 利用上下文管理器计算代码块的运行时间
1
2
3
4
5
6
7
8
import time
class CodeRunTime:
    def __enter__(self):
        self.start = time.perf_counter()

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f'代码块运行时间:{time.perf_counter() - self.start}')
        return False
1
2
3
4
with CodeRunTime():
    lst = []
    for i in range(1000000):
        lst.append(i)
1
代码块运行时间:0.1913649619998523

8.3 数据库编程

8.3.1 数据库应用编程接口

  • 数据库访问过程

  • 数据库适配器
  • 是数据库管理系统在 Python 中的驱动应用程序,作为客户端与数据库服务器进行通信
  • Python 官方给出了适配器 开发的统一规范,即数据库应用编程接口(DB-API),该规范由 PEP249 定义

  • 数据库应用编程接口(PEP249)

类型 名称 功能
全局属性 apilevel 字符串,兼容的 DB-API 版本(1.0 或 2.0)
threadsafety 线程安全级别(0 至 5)
paramstyle SQL 语句中占位符的风格(五种类型)
函数 connect 建立并返回数据库连接 (Connection) 对象
类.方法 Connection .close 关闭数据库连接
.commit 提交事务或操作
.rollback 回滚事务
.cursor 获取游标 (Cursor) 对象
Cursor .description 游标状态描述信息(属性)
.rowcount 最近一次操作的影响行数 (属性)
.callproc 调用数据库存储过程
.close 关闭游标
.execute 执行 SQL 语句
.executemany 利用一组参数多次执行同一 SQL 语句
.fetchone 获取查询结果中的下一条记录
.fetchmany 获取查询结果中指定数量的记录
.fetchall 获取查询结果中的剩余的全部记录
异常类 InterfaceError 数据库接口错误
DataError 数据处理错误
OperationalError 数据库操作执行错误
IntegrityError 数据库完整性错误
InternalError 数据库内部错误
ProgrammingError SQL错误
NotSupportedError 操作不被支持错误
  • DB-API 的 paramstyle 属性
取值 SQL 参数风格示例
'qmark' ...WHERE name=?
'numeric' ...WHERE name=:1
'named' ...WHERE name=:name
'format' ...WHERE name=%s
'pyformat' ...WHERE name=%(name)s
  • PEP249 是一种官方建议的数据库适配器工具包开发规范,不具有强制性,实际的数据库适配器对 PEP249 规范的遵循程度不一致

8.3.2 嵌入式数据库编程

  • 嵌入式数据库编程的一般步骤:
  • 建立数据库连接
  • 获取游标
  • 执行 SQL 语句
  • 提交事务或操作
  • 关闭游标和数据库连接

  • 创建sqlite数据库和表

1
2
3
4
5
6
7
import sqlite3
conn = sqlite3.connect('test.db')          # 建立数据库连接
cur = conn.cursor()                        # 获取游标
sql = 'create table users(id int primary key, name varchar, email varchar)'
cur.execute(sql)                           # 执行SQL语句
cur.close()                                # 关闭游标
conn.close()                               # 关闭数据库连接
  • 数据的添加、修改和删除操作
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import sqlite3
conn = sqlite3.connect("test.db")  # 建立数据库连接
cur = conn.cursor()                # 获取游标

sql = "insert into users values(1, '张三', 'zhangsan@test.com')"
cur.execute(sql)                   # 添加一条数据

sql = "insert into users values(?,?,?)"
data = [(2, '李四', 'lisi@test.com'),
        (3, '王五', 'wangwu@test.com'),
        (4, '赵六', 'zhaoliu@test.com')]
cur.executemany(sql, data)         # 添加多条数据

sql = "update users set email='lisi_new@test.com' where id=2"
cur.execute(sql)                   # 修改数据

sql = "delete from users where id=3"
cur.execute(sql)                   # 删除数据
conn.commit()                      # 提交操作
cur.close()
conn.close()
  • 数据查询操作
1
2
3
4
5
6
7
8
9
import sqlite3

sql = 'select * from users'
with sqlite3.connect("test.db") as conn:
    cur = conn.cursor()
    cur.execute(sql)                # 执行查询语句
    records = cur.fetchall()        # 获取查询结果
for row in records:
    print(row)
1
2
3
(1, '张三', 'zhangsan@test.com')
(2, '李四', 'lisi_new@test.com')
(4, '赵六', 'zhaoliu@test.com')

8.4 正则表达式*

  • 尽管Python的字符串处理方法非常强大,但还是难以应对一些复杂的字符串处理任务
  • 判断一个邮箱地址、手机号码是否合法
  • 提取出HTML文档中所有需要的数据
  • 设计思想
  • 利用一种描述性语言定义规则,然后利用该规则来匹配字符串或字符串文档,找出所有满足规则的部分
  • 应用场景
  • 匹配性检查,即判断某个字符串是否匹配特定的模式
  • 文本提取,即提取字符串文本中与规则相匹配的内容
  • 切分字符串,即将字符串文本在与规则相匹配的位置进行切分
  • 字符串替换,即将字符串文本中与规则相匹配的内容进行替换
  • Python的re模块提供了正则表达式功能

8.4.1 正则表达式匹配规则

  • 字符匹配
  • 边界匹配
  • 重复限制
  • 分组
  • 后向引用
  • 前置条件和后置条件

  • 字符匹配

符号 功能 规则示例 匹配示例
. 匹配\n之外任一字符 r'.' 'A'
\d 匹配一个数字 r'\d' '9'
\D 匹配一个非数字 r'\D' 'A'
\w 匹配一个字母、数字或下划线 r'\w' '_'
\W 匹配一个\w之外的字符 r'\W' '\t'
\s 匹配任何空白字符 r'\s' '\n'
\S 匹配任何非空白字符 r'\S' 'A'
x\|y 匹配xy r'a\|b\|c' 'a'
[xyz] 匹配xyz中的任一字符 r'[abc]' 'a'
[x-z] 匹配xz之间的任一字符 r[X-Z] 'Y'
[^xyz] 匹配xyz之外的任一字符 r[^1-9] 'A'
  • 边界匹配
符号 功能 规则示例 匹配示例 不匹配示例
^ 行头 r'^Py' 'the\nPython' 'the Python'
$ 行尾 r'the$' 'the\nPython' 'the Python'
\b 单词边界 r'\bPy' 'the Python' 'the-Python'
\B 不是单词边界 r'\BPy' 'the-Python' 'the Python'
\A 字符串头 r'\A'Py 'Python coding' 'the\nPython'
\Z 字符串尾 r'on\Z' 'the Python' 'Python coding'
  • 重复限制
符号 功能 规则示例 匹配示例 不匹配示例
* 重复零次或多次 r'\d*' '123abc'
+ 重复一次或多次 r'\d+' '123abc' 'abc'
? 重复零次或一次 r'\d?' '1abc'
{n} 重复n次 r'\d{3}' '123abc' '12abc'
{n,m} 重复最少n次最多m次 r'\d{2,3}' '123abc' '1abc'
{n,} 最少重复n次 r'\d{2,}' '12abc' '1abc'
{,m} 最多重复m次 r'\d{,2}' '12abc' '123abc'
  • 贪婪匹配与懒惰匹配
  • 使用重复限制后,正则表达式规则在匹配文本时会出现匹配内容长度不能确定的问题

例如,规则r'\w{3,5}'在匹配字符串'abcde'时,它的三个子串'abc''abcd''abcde'都附合规则,那么它匹配到的究竟是哪个子串呢?

Python正则表达式默认情况下匹配到的是最长的子串,这种方式称为__贪婪匹配__。 - 重复限制符号后添加?即表示待重复部分为__懒惰匹配__

符号 功能
*? 重复零次或多次,但次数尽可能少
+? 重复一次或多次,但次数尽可能少
?? 重复零次或一次,但次数尽可能少
{n}? 重复n次,但次数尽可能少
{n,m}? 重复最少n次最多m次,但次数尽可能少
  • 分组与后向引用
  • 正则表达式中使用()将多个符号作为一个分组 例如,规则r'(\d{1,3}\.){3}\d{1,3}'用于匹配IP地址
  • 分组匹配到的文本内容编号并保存在缓存之中
    • 编号方式为:\1表示匹配到的第1个内容,\2表示匹配到的第2个内容,... ...
    • 缓存数据可实现文本内容的提取
    • 缓存的数据可以被正则表达式后续部分引用,称为__后向引用__
    • 在分组中添加?:表示不缓存匹配内容,例如r(?:\d{1,3})
  • 命名分组

    • Python中命名分组的方式为(?P<group_name>)
    • 后向引用中使用分组名(?P=group_name)
  • 前置条件和后置条件

规则 说明 规则示例 匹配示例 不匹配示例
r'(?=sub_rules)' 后置条件 r'abc(?=[de])' 'abcd''abce' 'abcf'
r'(?!sub_rules)' 后置非条件 r'abc(?![de])' 'abcf' 'abcd''abce'
r'(?<=sub_rules)' 前置条件 r'(?<=[ab])cde' 'acde''bcde' 'fcde'
r'(?<!sub_rules)' 前置非条件 r'(?<![ab])cde' 'fcde' 'acde''bcde'

8.4.2 正则表达式的应用

匹配性检查

  • 实现
  • re.match函数
  • re.search函数
  • Match对象
  • start
  • end
  • span
  • group
  • groups
  • groupdict
1
2
3
4
from re import match
rule = r'^\w{3,}@\w+\.(?P<suffix>com|cn|net)$'
result = match(rule, 'test@mail.com')
result
1
<re.Match object; span=(0, 13), match='test@mail.com'>
1
2
3
print(result.start())
print(result.end())
print(result.span())
1
2
3
0
13
(0, 13)
1
result.group(0, 1)   # 返回一个或多个分组(0号和1号分组)
1
('test@mail.com', 'com')
1
result.groups()      # 返回所有在规则中指定的分组
1
('com',)
1
result.groupdict()   # 返回命名分组构成的字典
1
{'suffix': 'com'}
  • matchsearch都只返回字符串中第一次匹配到的结果,即其中有多个满足规则的内容
  • 两者的区别在于,当字符串包含多行时,match只会搜索第一行,而search则会搜索整个字符串
1
2
3
4
from re import search
rule = r'\w{3,}@\w+\.(com|cn|net)'
text = 'my email is :\n test@mail.com'   # 多行字符串
match(rule, text) is None
1
True
1
search(rule, text)
1
<re.Match object; span=(15, 28), match='test@mail.com'>

文本提取

  • 实现
  • re.findall
    • 会搜索字符串文本,并返回所有与规则相匹配的内容构成的列表
  • re.finditer
    • 返回一个生成器
1
2
3
4
5
from re import findall
rule = r'(\w{3,}@\w+\.(com|cn|net))'
text = ''' I have two emials. They are test1@mail1.com
                                   and test2@mail2.net.'''
findall(rule, text)
1
[('test1@mail1.com', 'com'), ('test2@mail2.net', 'net')]
1
2
rule = r'(\w{3,}@\w+\.(?:com|cn|net))'  # 不缓存不需要的分组
findall(rule, text)
1
['test1@mail1.com', 'test2@mail2.net']

切分字符串

  • 实现
  • re.split
1
2
3
4
from re import split
text = 'a,b;c.d e|f-g'
rule = r'[,\;\.\ |-]'
split(rule, text)
1
['a', 'b', 'c', 'd', 'e', 'f', 'g']

字符串替换

  • 实现
  • re.sub
    • 返回替换后的字符串
  • re.subn
    • 返回由替换后的字符串和替换次数组成的元组
1
2
3
4
from re import sub
text = 'I have two emials. They are test1@mail1.com and test2@mail2.net.'
rule = r'(\w{3,})(?=@\w+\.(com|cn|net))'
sub(rule, '***', text)
1
'I have two emials. They are ***@mail1.com and ***@mail2.net.'

8.4.3 正则表达式的编译

  • 正则表达式的使用过程
  • 1 编译
  • 2 匹配
  • 每次调用都会重新编译一次
  • 提高效率的方式
  • 编译和匹配分开
  • 实现
  • re.compile
1
2
3
4
from re import compile
text = 'I have two emials. They are test1@mail1.com and test2@mail2.net.'
regex = compile(r'(\w{3,})(?=@\w+\.(com|cn|net))')
regex.sub('***', text)
1
'I have two emials. They are ***@mail1.com and ***@mail2.net.'

8.5 数据分析中的数据结构 *

8.5.1 NumPy

  • NumPy 是一种高效的矩阵/高维数组计算工具包,几乎所有的数据 分析和处理工具都依赖或支持 NumPy

ndarray

  • NumPy 中最核心的数据类型
  • 创建ndarray常常使用 Numpy 中的array函数
1
2
3
import numpy as np
nda = np.array([[1, 4, 2],[8, 5, 7]])
type(nda)
1
numpy.ndarray
1
nda
1
2
array([[1, 4, 2],
       [8, 5, 7]])
  • 创建特殊结构的ndarray
函数 功能描述
zeros 创建全 0 的多维数组
ones 创建全 1 的多维数组
eye 创建对角线元素为 1 的二维数组
random.rand 以随机生成的数据创建多维数组
1
2
import numpy as np
np.zeros(shape=[3,3])   # 3行3列元素为0的多维数组
1
2
3
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])
1
np.ones(shape=[3,3])    # 3行3列元素为1的多维数组
1
2
3
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]])
1
np.eye(3)               # 3阶单位矩阵
1
2
3
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
1
np.random.rand(3,3)     # 3行3列由随机数字组成的多维数组
1
2
3
array([[0.93886107, 0.05554724, 0.07318204],
       [0.81070672, 0.99872441, 0.61734829],
       [0.8443944 , 0.65327837, 0.93905703]])

多维数组的操作

  • 元素访问与过滤
1
2
3
import numpy as np
nda = np.random.rand(5)
nda
1
array([0.45003522, 0.88112387, 0.00835263, 0.20844166, 0.81008449])
1
nda[[0, 2, 4]] # 获取索引为0、2、4的三个元素
1
array([0.45003522, 0.00835263, 0.81008449])
1
nda[nda>0.3] # 获 取 大 于0.3的 元 素
1
array([0.45003522, 0.88112387, 0.81008449])
  • 改变形状与维度
1
2
nda = np.array([[11, 12],[21, 22], [31, 32]])
nda
1
2
3
array([[11, 12],
       [21, 22],
       [31, 32]])
1
nda.reshape(2, 3)      # 转换为2行3列
1
2
array([[11, 12, 21],
       [22, 31, 32]])
1
nda.transpose(1, 0)    # 转置
1
2
array([[11, 21, 31],
       [12, 22, 32]])
  • 元素运算
1
np.sin(nda)           # 每个元素求正弦值
1
2
3
array([[-0.99999021, -0.53657292],
       [ 0.83665564, -0.00885131],
       [-0.40403765,  0.55142668]])
1
np.average(nda)       # 均值
1
21.5

矩阵运算

1
2
3
4
5
import numpy as np
A = np.array([[1, 2],
              [3, 4]])
B = np.array([[1, 1],
              [1, 1]])
1
A + B                  # 矩阵求和
1
2
array([[2, 3],
       [4, 5]])
1
A.dot(B)               # 矩阵运算
1
2
array([[3, 3],
       [7, 7]])
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
As = np.array([[[1, 2],
                [3, 4]],
               [[1, 2],
                [3, 4]],
              ])
Bs = np.array([[[1, 1],
                [1, 1]],
               [[1, 1],
                [1, 1]],
               ])
np.matmul(As, Bs)        # 批量矩阵运算
1
2
3
4
5
array([[[3, 3],
        [7, 7]],

       [[3, 3],
        [7, 7]]])

广播

  • 数学中的矩阵运算要求两个矩阵的形状必须相匹配
  • 在高维矩阵运算中可能会带来不必要的计算量和存储空间
  • 广播(Broadcast)
  • 是 NumPy 中的一种特有的运算机制,目的就是适当放松矩阵运算中必须要求形状相匹配的条件,提升运算效率
1
2
3
4
5
6
7
8
9
import numpy as np
As = np.array([[[1, 2],            # As为3维数组
                [3, 4]],
               [[1, 2],
                [3, 4]],
               ])
B = np.array([[1, 1], [1, 1]])     # B为2维数组

As + B
1
2
3
4
5
array([[[2, 3],
        [4, 5]],

       [[2, 3],
        [4, 5]]])
1
As.dot(B)
1
2
3
4
5
array([[[3, 3],
        [7, 7]],

       [[3, 3],
        [7, 7]]])
  • NumPy 多维数组运算中,一旦发生形状不匹配的情况就会自动触发广播
  • 广播的逻辑本质,是在特定维度上复制数组使得其形状相匹配
  • 广播相关概念
  • 轴(Axes)
    • NumPy 多维数组中的一个维度称为一个轴
  • 轴长
    • 一个轴上数据元素的数量称为维度的轴长
  • 后尾维度(Trailing Dimension)

    • 高维数组的最后一个或多个连续的轴
  • As和B具有相同的后尾维度

1
As.shape # As的形状是由其3个有序的轴的长度构成的元组
1
(2, 2, 2)
1
B.shape #B的形状 
1
(2, 2)
  • 广播的条件
  • 当数组维度不同时,后尾维度的轴长(或形状)相符
  • 当数组维度相同时,其中之一拥有至少一个轴长为 1 的维度
1
2
B = np.array([[[1, 1], [1, 1]]])
As + B
1
2
3
4
5
array([[[2, 3],
        [4, 5]],

       [[2, 3],
        [4, 5]]])
1
As.dot(B)
1
2
3
array([[[[3, 3]],

        [[7, 7]]],


[[[3, 3]],

1
        [[7, 7]]]])

8.5.2 SciPy

  • SciPy 是一个常用于数学、科学、工程领域的工具包,可用于解决数值计算、积分、 优化、图像处理、常微分方程数求解、信号处理等问题
  • SciPy 依赖于 NumPy

特征值与特征向量求解

  • 矩阵\(\mathbf A\)的特征值和特征向量分别是满足下式的向量\(\mathbf v\)和标量\(\lambda\)
\[ \mathbf A \mathbf v = \lambda \mathbf v \]
  • 可利用scipy.linalg中的eig函数来求特征值与特征向量
1
2
3
4
5
6
7
from scipy import linalg

A = np.array([[6, 2, 4],
              [2, 3, 2],
              [4, 2, 6]])
lmds, vs = linalg.eig(A)
lmds       # 特征值
1
array([ 2.+0.j, 11.+0.j,  2.+0.j])
1
vs         # 每一列是一个特征向量
1
2
3
array([[-0.74535599,  0.66666667, -0.34869867],
       [ 0.2981424 ,  0.33333333, -0.65103258],
       [ 0.59628479,  0.66666667,  0.67421496]])

奇异值分解

  • 奇异值分解是将形如\(m\times n\)的矩阵\(\mathbf A\)分解为如下形式:
\[ \mathbf A = \mathbf U\Sigma \mathbf V \]

其中\(\mathbf U\)\(\mathbf V\)为分别为\(m\times m\)\(n\times n\)的正交矩阵 (\(\mathbf{UU}^{\top}=\mathbf I\)\(\mathbf{VV}^{\top}=\mathbf I\)), 分别称为左奇异矩阵和右奇异矩阵;\(\Sigma\)为仅主对角线元素有非零值的\(m\times n\)矩阵, 这些非零值称为奇异值

  • 奇异值分解在数据降维和压缩、信息推荐、数据去噪等领域有重要的应用价值
  • 可利用scipy.linalg中的svd函数来对矩阵进行奇异值分解
1
2
3
4
5
from scipy import linalg
A = np.array([[1,  5,  7,  6,  1],
              [2,  1, 10,  4,  4],
              [3,  6,  7,  5,  2]])
U, Sigma, V = linalg.svd(A)
1
U        # 左奇异矩阵
1
2
3
array([[-0.55572489,  0.40548161, -0.72577856],
       [-0.59283199, -0.80531618,  0.00401031],
       [-0.58285511,  0.43249337,  0.68791671]])
1
Sigma   # 奇异值
1
array([18.53581747,  5.0056557 ,  1.83490648])
1
V       # 右奇异矩阵
1
2
3
4
5
array([[-0.18828164, -0.37055755, -0.74981208, -0.46504304, -0.22080294],
       [ 0.01844501,  0.76254787, -0.4369731 ,  0.27450785, -0.38971845],
       [ 0.73354812,  0.27392013, -0.12258381, -0.48996859,  0.36301365],
       [ 0.36052404, -0.34595041, -0.43411102,  0.6833004 ,  0.30820273],
       [-0.5441869 ,  0.2940985 , -0.20822387, -0.0375734 ,  0.7567019 ]])

稀疏矩阵

  • 稀疏矩阵
  • 矩阵中绝大多数元素是零或者是接近0的数字
  • 稀疏矩阵的存储方式
  • SciPy 的sparse 模块中提供了7种类型的稀疏矩阵
类型 结构特点 优点 缺点
coo_matrix 采用(行, 列, 数据)三 元组形式存储数据 创建方便,结构转换快 不支持运算和切片操作
dok_matrix 基于字典存储数据 创建方便,结构转换快 运算效率低,不支持切片操作
csr_matrix 按行压缩存储数据 运算效率高,行切片效率高 列切片效率低,结构转换慢
csc_matrix 按列压缩存储数据 运算效率高,列切片效率高 行切片效率低,结构转换慢
bsr_matrix 采用分块方式存储数据 运算效率高,适用于有密集子矩阵的稀疏矩阵 切片效率低,结构转换慢
lil_matrix 采用嵌套列表存储数据 行切片效率高,结构转换快 运算效率低,列切片效率低
dia_matrix 按对角线存储数据 运算效率高,对角性良好时存储效率高 对角性不好时存储效率低,不 支持切片操作
  • coo_matrix
1
2
3
4
5
6
from scipy import sparse
row_index = [0, 1, 1, 2, 3, 4]
col_index = [1, 2, 4, 2, 3, 2]
data = [1, 2, 3, 4, 5, 6]
m_coo = sparse.coo_matrix((data, (row_index, col_index)), dtype=float)
type(m_coo)
1
scipy.sparse.coo.coo_matrix
1
print(m_coo)                      # 显示矩阵元素
1
2
3
4
5
6
  (0, 1)    1.0
  (1, 2)    2.0
  (1, 4)    3.0
  (2, 2)    4.0
  (3, 3)    5.0
  (4, 2)    6.0
1
m_coo.todense()                   # 转密度矩阵
1
2
3
4
5
matrix([[0., 1., 0., 0., 0.],
        [0., 0., 2., 0., 3.],
        [0., 0., 4., 0., 0.],
        [0., 0., 0., 5., 0.],
        [0., 0., 6., 0., 0.]])
  • csr_matrix
1
2
3
4
from scipy.sparse import linalg

m_csr = m_coo.tocsr()             # 将coo_matrix转为csr_matrix
type(m_csr)
1
scipy.sparse.csr.csr_matrix
1
2
3
m_trans = m_csr.transpose()       # 稀疏矩阵转置
mm = m_csr.dot(m_trans)           # 稀疏矩阵矩阵相乘
mm.todense()                      # 将稀疏矩阵转为密度矩阵
1
2
3
4
5
matrix([[ 1.,  0.,  0.,  0.,  0.],
        [ 0., 13.,  8.,  0., 12.],
        [ 0.,  8., 16.,  0., 24.],
        [ 0.,  0.,  0., 25.,  0.],
        [ 0., 12., 24.,  0., 36.]])

8.6 数据可视化*

  • Matplotlib是Python中著名的绘图库。它最初是对Matlab绘图命令的模仿,目前是Python绘图领域影响最为广泛的工具

8.6.1 简单绘图

  • pyplot模块定义了大量函数用于绘制、配置或修改图像
函数 功能
plot 折线图
scatter 散点图
bar 柱状图
pie 饼图
hist 直方图
imshow 热力图
1
2
3
4
5
6
import matplotlib.pyplot as plt
#(a)折线图
x = range(10)
y = [i**2 for i in x]
plt.plot(x, y)
plt.show()


png

1
2
3
4
5
#(b)散点图
x = range(10)
y = [i**2 for i in x]
plt.scatter(x, y)
plt.show()


png

1
2
3
4
5
#(c)柱状图
x = range(10)
y = [i**2 for i in x]
plt.bar(x, y)
plt.show()


png

1
2
3
4
#(d)饼图
numbers = [0.1, 0.4, 0.3, 0.2]
plt.pie(numbers)
plt.show()


png

1
2
3
4
#(e)直方图
x = [1, 1, 2, 2, 2, 2, 3, 3]
plt.hist(x)
plt.show()


png

1
2
3
4
#(f)热力图
X = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
plt.imshow(X)
plt.show()


png

8.6.2 图像的配置与修饰

  • Matplotlib图像的主要组成部分
  • 图像:是一个Figure对象。常用的属性包括图像的大小(figsize)、分辨率(dpi)等,可包含一个或多个坐标轴
  • 坐标轴:图像的子图,是一个AxesSubplot对象。常用属性有标题(title)等,每个坐标轴由 x 轴(xaxis)和 y 轴(yaxis)组成,每个轴都可以配置自己的标识 (xlabelylabel)、刻度、刻度范围、刻度标识等
  • 绘图:具体的图形对象,可以是折线图、散点图等。属性有线形、宽度、颜色、修饰符(marker)及其形状和颜色等
  • 填充:在折线图与坐标轴之间(坐标轴的fill方法)或者不同的折线之间 (坐标轴的fill_between方法)填充颜色
  • 图例:根据绘图的线形、颜色、修饰符等自动生成
  • 标注:使用坐标轴的annotate方法或text方法添加文本或箭头等形状,文本中支持 Latex 符号和公式

 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
30
31
32
33
34
35
36
37
38
39
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator

font = {'family': 'simhei', 'size':12}  # 中文字体和字号
# 图像大小和分辨率
plt.figure(figsize=[8, 6], dpi=100)

# 折线图
x = np.arange(-10, 10.1, 0.1)
y1 = x ** 2
y2 = np.array([64] * len(x))
plt.plot(x, y1, label='$y=x^2$', marker='s', markevery=20)
plt.plot(x, y2, label='$y=64$', marker='o', markevery=20)

# 区域填充
x_fill = np.arange(-8, 8, 0.1)
y1_fill = x_fill ** 2
y2_fill = np.array([64]*len(x_fill))
plt.fill_between(x_fill, y1_fill, y2_fill, alpha=.2)

# 坐标轴配置
plt.xlim(-11, 11)      # x轴刻度区间
plt.ylim(-5, 105)      # y轴刻度区间
ax = plt.gca()         # 获取坐标轴对象
# ax.set_xticks(range(-10, 11, 5))   # 设置x轴刻度
# ax.set_yticks(range(-0, 101, 10))  # 设置y轴刻度
ax.xaxis.set_major_locator(MultipleLocator(5))  # x轴主刻度
ax.xaxis.set_minor_locator(MultipleLocator(1))  # x轴副刻度
ax.yaxis.set_major_locator(MultipleLocator(10)) # y轴主刻度
ax.yaxis.set_minor_locator(MultipleLocator(5))  # y轴副刻度

plt.annotate('$y=x^2$', xy=(-9, 81), xytext=(-7, 90),  # 标注
            arrowprops={'arrowstyle': "->"})
plt.xlabel('x轴标识(xlabel)', fontdict=font)    # x轴标识
plt.ylabel('y轴标识(ylabel)', fontdict=font)    # y轴标识
plt.title("标题(title)", fontdict={'family': 'simhei'})  # 图像标题
plt.legend(loc='upper right')                   # 图例
plt.show()                                      # 显示图像


png

  • 中美新冠病毒确诊数量对比
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rc('font', family='simsun')          # 设置字体
china = [11791, 79824, 81554, 82874, 83017, 83534, 84337,
         85058, 85414, 85997, 86542, 87071]
usa = [11, 66, 140640, 1003974, 1734040, 2537636, 4388566,
       5899504, 7077015, 8852730, 13082877, 19346790]
mothons = ['1月', '2月', '3月', '4月', '5月', '6月',
           '7月', '8月', '9月', '10月', '11月', '12月']
plt.plot(china, label='中国', marker='o')
plt.plot(usa, label='美国', marker='s')
plt.legend()
plt.xticks(range(0, 12), mothons, rotation=45)  # 设置x轴坐标标签
plt.yscale('log')                               # 设置y轴为对数坐标
plt.title('中美新冠病毒确诊数量对比', fontdict={'size': 16})
plt.show()


png

8.6.3 多子图图像的绘制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import matplotlib.pyplot as plt
plt.subplot(2, 2, 1)  # 2行2列第 1 幅子图像
plt.text(0.5, 0.5, 'subplot(2,2,1)', ha='center', va='center', size=15)
plt.subplot(2, 2, 2)  # 2行2列第 2 幅子图像
plt.text(0.5, 0.5, 'subplot(2,2,2)', ha='center', va='center', size=15)
plt.subplot(2, 2, 3)  # 2行2列第 3 幅子图像
plt.text(0.5, 0.5, 'subplot(2,2,3)', ha='center', va='center', size=15)
plt.subplot(2, 2, 4)  # 2行2列第 4 幅子图像
plt.text(0.5, 0.5, 'subplot(2,2,4)', ha='center', va='center', size=15)
plt.subplots_adjust(left=0.08, bottom=0.08, right=0.96, top=0.96, 
                    wspace=0.3, hspace=0.3)  # 调整图像的边距和间距
plt.show()


png

8.6.4 三维图像的绘制

  • Matplotlib的mpl_toolkits辅助模块中提供了各种三维图像的绘制功能
  • 函数\(Z=\sin(\sqrt{X^2+Y^2})\)的三维图像
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
mpl.rcParams['axes.unicode_minus']=False  # 使负号正常显示

fig = plt.figure()
ax = Axes3D(fig)

X = np.arange(-6, 6, 0.1)
Y = np.arange(-6, 6, 0.1)
X, Y = np.meshgrid(X, Y)
Z = np.sin(np.sqrt(X**2 + Y**2))

ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='coolwarm')
plt.show()


png

8.7 Pandas基础*

  • Pandas是Python数据分析与处理领域非常知名的工具之一,常用于结构化数据分析任务,例如数据分析、数据挖掘和数据清洗等
  • 从功能上来说可以粗略地认为它是电子表格和结构化查询语言(SQL)相结合的Python实现

8.7.1 数据结构

系列(Series

  • 系列本质上是带有索引的一维数组元素可以是整数、浮点数、字符串,以及其他的 Python 对象
  • 可以基于列表、字典或者 Numpy 数组来创建一个系列
1
2
3
4
5
6
import pandas as pd
import numpy as np

lst = [1, 4, 2]
s1 = pd.Series(lst)                        # 由列表创建系列
s1
1
2
3
4
0    1
1    4
2    2
dtype: int64
1
2
s2 = pd.Series(np.array(lst))              # 由Numpy数组创建系列
s2
1
2
3
4
0    1
1    4
2    2
dtype: int64
1
2
s3 = pd.Series(lst, index=['a', 'b', 'c']) # 系列的标签索引
s3
1
2
3
4
a    1
b    4
c    2
dtype: int64
1
2
3
dic = {'a': 1, 'b': 2, 'c': 3}
s4 = pd.Series(dic, dtype=float)           # 由字典创建系列
s4
1
2
3
4
a    1.0
b    2.0
c    3.0
dtype: float64

数据框

  • 普通的数据框从逻辑上来说是一种二维表格
  • 可以基于二维列表或二 维 Numpy 数组创建数据框,也可以基于系列来创建数据框
1
2
3
lst = [[1, 8],[4, 5],[2, 7]]
df1 = pd.DataFrame(lst, index=['a', 'b', 'c'])  # 由列表创建数据框
df1
0 1
a 1 8
b 4 5
c 2 7
1
2
3
df2 = pd.DataFrame(np.array(lst),               # 由Numpy数组创建数据框,
                   columns=['s1', 's2'])        # 并指定列名
df2
s1 s2
0 1 8
1 4 5
2 2 7
1
2
3
4
s1 = pd.Series([1, 4, 2])
s2 = pd.Series([8, 5, 7])
df3 = pd.DataFrame({'s1': s1, 's2': s2})  # 由系列创建数据框
df3
s1 s2
0 1 8
1 4 5
2 2 7
1
2
df3 = df3.append({'s1': 0, 's2':0}, ignore_index=True)  # 添加行
df3
s1 s2
0 1 8
1 4 5
2 2 7
3 0 0
1
2
df3['new'] = df3.s1 + df3.s2              # 添加列
df3
s1 s2 new
0 1 8 9
1 4 5 9
2 2 7 9
3 0 0 0
  • 数据框的读入和写出
  • Pandas支持多种格式数据的读入和写出,常用的格式包括Excel、CSV、JSON、SQL、HDF、STATA、SAS、SPSS等。
1
2
3
df3.to_csv('test.csv', index=False)  # 保存为CSV格式(不保存索引)
df4 = pd.read_csv('test.csv')        # 读入CSV格式创建数据框
df4
s1 s2 new
0 1 8 9
1 4 5 9
2 2 7 9
3 0 0 0

8.7.2 数据访问

索引访问

1
s4[1]
1
2.0
1
s4[0:2]
1
2
3
a    1.0
b    2.0
dtype: float64
1
s4['a']
1
1.0
1
s4[[0, 2]]
1
2
3
a    1.0
c    3.0
dtype: float64
1
s4[['a', 'c']]
1
2
3
a    1.0
c    3.0
dtype: float64
  • 数据框的索引访问常用到三个属性
  • loc: 使用标签索引来访问一行或多行
  • iloc: 使用整数索引来访问一行或多行
  • iat: 通过指定行和列来访问数据框元素
1
2
3
lst = [[1, 8],[4, 5],[2, 7]]
df = pd.DataFrame(lst, index=['a', 'b', 'c'], columns=['c1', 'c2'])
df
c1 c2
a 1 8
b 4 5
c 2 7
1
df.c1                    # 访问c1列,等同于df['c1']
1
2
3
4
a    1
b    4
c    2
Name: c1, dtype: int64
1
df.loc[['a', 'b']]       # 访问a和b两行
c1 c2
a 1 8
b 4 5
1
df.loc[['a', 'b'], 'c1'] # 访问a和b两行的c1列
1
2
3
a    1
b    4
Name: c1, dtype: int64
1
df.iloc[0:3:2]           # 行切片,等于与df[0:3:2]
c1 c2
a 1 8
c 2 7
1
df.iloc[0:2, 0]          # 访问0到1行第0列
1
2
3
a    1
b    4
Name: c1, dtype: int64
1
df.iat[0, 1]             # 访问位于第0行第1列的元素
1
8

条件选择

  • 根据指定的条件对系列或数据框的值进行过滤
1
2
lst = [[1, 8],[4, 5],[2, 7]]
df = pd.DataFrame(lst, index=['a', 'b', 'c'], columns=['c1', 'c2'])
1
df[df.c1>1]                 # c1列大于1的行
c1 c2
b 4 5
c 2 7
1
df[(df.c1>1) & (df.c2>5)]   # c1列大于1且c2列大于5的行
c1 c2
c 2 7
1
df[(df.c1>2) | (df.c2>7)]   # c1列大于2或c2列大于7的行
c1 c2
a 1 8
b 4 5

迭代

  • 数据框可以生成多种类型的迭代器,能够对行进行不同形式的迭代遍历
1
2
3
4
lst = [[1, 8],[4, 5],[2, 7]]
df = pd.DataFrame(lst, index=['a', 'b', 'c'], columns=['c1', 'c2'])
for v in df.c1:               # 遍历系列
    print(v)
1
2
3
1
4
2
1
2
for i, r in df.iterrows():    # 遍历数据框的行
    print(i, r[0], r[1])
1
2
3
a 1 8
b 4 5
c 2 7
1
2
for r in df.itertuples():     # 遍历数所框的行
    print(r)
1
2
3
Pandas(Index='a', c1=1, c2=8)
Pandas(Index='b', c1=4, c2=5)
Pandas(Index='c', c1=2, c2=7)

统计分析

  • 系列数据常用统计方法
函数 功能
count 数据量
sum 求和
mean 均值
median 中位数
std 准偏差
min 最小值
max 最大值
value_counts 频次统计
  • 数据框的描述信息
1
2
3
lst = [[1, 8],[4, 5],[2, 7]]
df = pd.DataFrame(lst, index=['a', 'b', 'c'], columns=['c1', 'c2'])
df.describe()
c1 c2
count 3.000000 3.000000
mean 2.333333 6.666667
std 1.527525 1.527525
min 1.000000 5.000000
25% 1.500000 6.000000
50% 2.000000 7.000000
75% 3.000000 7.500000
max 4.000000 8.000000
  • 协方差和相关系数
1
2
3
data = np.random.randn(100, 2)
df = pd.DataFrame(data, columns=['c1', 'c2'])
df.c1.cov(df.c2)            # c1列与c2列的协方差 
1
-0.01931279550960339
1
df.cov()                    # df中各列的协议差矩阵
c1 c2
c1 0.853200 -0.019313
c2 -0.019313 0.715135
1
df.c1.corr(df.c2)           # c1列与c2列的相关系数
1
-0.024724395919958092
1
df.corr(method='pearson')   # df中各列的相关矩阵
c1 c2
c1 1.000000 -0.024724
c2 -0.024724 1.000000

8.8 Scikit-learn基础*

8.8.1 Scikit-learn简介

  • Scikit-learn是一种通用的机器学习工具包,它依赖 NumPy、SciPy 和 Matplotlib 以实现高效的数值过算、优化和可视化
  • Scikit-learn对整个机器学习流程提供了完整的支持,例如数据集读取、数据处理与 转换、模型及学习方法、模型的评价与选择等
  • Scikit-learn的核心功能
功能 描述
有监督学习模型 分类模型、回归模型、集成学习模型、有监督神经网络模型、半监督学习模型,以及特征选择工具
无监督学习模型 聚类模型、双向聚类模型、包括高斯混合模型、流形学习、矩阵分解、变 分估计、异常检测、密度估计、有监督神经网络模型
模型选择与评价 交叉验证、调参工具、性能评价指标、模型保存、偏差/方差分析
模型解释 部分依赖图(PDP)、排列特征重要性(Permutation feature importance)
可视化 数据可视化、学习过程可视化、模型评价/选择可视化等
数据转换工具 特征提取、数据预处理、缺失值处理、数据降维、标签处理、核方法
数据集工具 内置数据集、数据集生成工具、数据集加载工具
高性能计算工具 大数据集处理、并行计算、资源管理与配置
  • Scikit-learn 将大多数机器学习功能封装为类并基于相同或相似的接口实现,主要可分为如下三种
  • Estimator:估计器,各常机器学习模型都被封装为估计器。绝大多数估计器都有如下三个方法:
    • fit(x, y):根据数据x及标签y对模型进行训练
    • score(x, y):根据数据x及标签y对训练好的模型进行评价
    • predict(x):利用训练好的模型预测数据x的标签
  • Transformer:转换器,对数据进行转换操作,如标准化、降维、特征选择等。大多数转换器都有如下三个方法:
    • fit(x, y):根据数据x和标签y确定数据转换方式
    • transform(x):根据确定的转换方式,对数据x进行转换
    • fit_transform(x, y):确定数据转换方式,并且对数据进行转换
  • Pipline
  • 将数据处理、转换到模型学习等多个步骤组装为一个完整的过程以便于 模型的训练和使用,通常会包含多个转换器和一个估计器。Pipline对象也有fit、 score、predict、fit_transform等方法。

8.8.2 分类问题

  • 分类问题
  • 给定一组类别已知的样本数据集(有监督数据),确定一个分类函数用于对类别未知的数据的所属类别进行预测
  • k 最近邻算法
  • step1:确定 k 值以及样本距离的定义方式
  • step2:对于数据集中的样本s,计算它与其他样本数据的距离
  • step3:确定距离最近的k个样本以及它们所属的类别c
  • step4:将s的类别标记为c
  • 鸢尾花数据集(Iris)
  • 每个样本对应着一朵鸢尾花样本,记录着花萼长度、花萼宽度、花瓣长度、花瓣宽度四个特征
  • 数据集中共包含三个类别(iris-setosa、iris-versicolour和iris-virginica)的鸢尾花样本
  • 每个类别共有50个样本数据

  • 鸢尾花分类的 k 最近邻模型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from sklearn import neighbors, datasets
from sklearn.utils import shuffle

iris = datasets.load_iris()                          # 加载数据集

X, y = shuffle(iris.data, iris.target)               # 随机打乱数据
X_train, X_test = X[:120], X[120:]                   # 训练集与测试集特征
y_train, y_test = y[:120], y[120:]                   # 训练集与测试集标签

knn = neighbors.KNeighborsClassifier(n_neighbors=10, # K最近邻估计器
                                     metric='minkowski', p=2)
knn.fit(X_train, y_train)                            # 训练

accuracy = knn.score(X_test, y_test)                 # 模型评价
print("测试集正确率:", accuracy)

d_id = 10
data_x = iris.data[d_id]
data_y = iris.target[d_id]
result = knn.predict([data_x])                       # 预测

print('预测样本:', data_x)
print('预测类别:', iris.target_names[result[0]])
print('真实类别:', iris.target_names[data_y])
1
2
3
4
测试集正确率: 0.9666666666666667
预测样本: [5.4 3.7 1.5 0.2]
预测类别: setosa
真实类别: setosa

8.8.3 聚类问题

  • 聚类问题
  • 给定一组样本数据根据相似度,将 它们划分入不同的类别,使得类内数据之间的距离尽可能小,类与类之间的距离尽可能 大
  • \(k\)均值模型
  • 第一,计算数据与聚类中心之间的距离,并将其划分入距离最近的中心表示的类别之中
  • 第二,根据新的数据类别划分结果,更新类别中心的位置
  • 重复这两个步骤,直到达到最大迭代次数或者类别中心的位置不再发生变化

  • \(k\)均值模型

 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
30
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs

font = {'family': 'simsun', 'size': 12}
plt.figure(figsize=[10, 5])

centers = [[0, 1], [-1, -1], [1, -1]]
X, labels_true = make_blobs(n_samples=300,              # 生成数据集
                            centers=centers, cluster_std=0.3)

# 画出原始数据
plt.subplot(1, 2, 1)
plt.scatter(X[:, 0], X[:, 1])
plt.xlabel('(a) 原始数据', fontdict=font)

k_means = KMeans(n_clusters=3)                          # K均值估计器
k_means.fit(X)                                          # 聚类

# 画出聚类结果
plt.subplot(1, 2, 2)
marker_list = ['o', 's', '^']
color_list = ['r', 'b', 'g']
for i in range(3):
    c_data = X[k_means.labels_ == i]  # 获取一个类别的数据
    plt.scatter(c_data[:, 0], c_data[:, 1],             # 画出一个类别
                c=color_list[i], marker=marker_list[i], alpha=0.6)

plt.xlabel('(b) 聚类结果', fontdict=font)
plt.show()


png