机器学习第二章之数据分析的基本武器

时间:2024-12-10 21:00:04

数据分析的武器:Numpy、pandas与Matplotlib库

    • 2.1 Numpy基础
      • 2.1.1 Numpy与数组
      • 2.1.2 Numpy数组与列表的区别
      • 2.1.3 创建数组的几种方式
    • 2.2 pandas基础
      • 2.2.1 二维数据表格DataFrame的创建
      • 2.2.2 Excel等文件的读取和写入(重要)
      • 2.2.3 数据读取与筛选(重要)
      • 2.2.4 数据表拼接
    • 2.3 Matplotlib数据可视化基础
      • 2.3.1 基本图形绘制
      • 2.3.2 数据可视化常见小技巧
    • 2.4 综合案例实战 - 股票数据读取与K线图绘制
      • 库的更新
      • 2.4.1 初步尝试 - 股票数据读取与可视化
      • 2.4.2 综合实战 - 股票K线图绘制
    • 2.5 课程相关资源

Python之所以强大,就是因为它提供了很多高效便捷的数据分析工具包,这一章变将讲解数据分析的三个常用的武器Numpy、pandas与Matplotlib库,其中Numpy库是pandas库的基础,它们主要是用来处理一维及二维的表格数据,而Matplotlib库这是数据可视化的利器,在本章最后,我们将会进行一个综合案例实战:股票数据读取与可视化分析。
这一章内容较多,在笔者第一本书《Python金融大数据挖掘与分析全流程详解》的基础上丰富了不少内容,推荐读者朋友第一次阅读的时候可以先有个大概的印象,在之后的学习中碰到不清楚的相关知识点,再来查阅本章。此外,本章全部学习完成后,可以通过这些数据分析的武器绘制下图所示的股票K线图。
在这里插入图片描述

2.1 Numpy基础

在学习pandas库之前,首先得了解一下Numpy库,它是Numerical Python的简称,也是pandas库的基础,我们所需要掌握的Numpy知识并不复杂,主要是为了给之后的pandas学习做铺垫。如果是利用Anaconda安装的Python,那么Anaconda自带Numpy库,无需单独安装。

2.1.1 Numpy与数组

Numpy库主要特点就是引入了数组的概念,数组其实和之前学过的列表有点类似,这里通过列表来初步认识下数组的基本概念。首先引入Numpy库,其引入方式通常写为import numpy as np,这样可以用np代替numpy了,之后写起来比较简洁,代码如下:

import numpy as np
a = [1, 2, 3, 4]
b = np.array([1, 2, 3, 4])
print(a)
print(b)
print(type(a))  # 打印a的类别
print(type(b))  # 打印b的类别
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其中**(列表)**为生成数组的一种方式,array就是数组的意思,其输出结果如下:

[1, 2, 3, 4] # 列表的展现形式
[1 2 3 4] # 数组的展现形式
<class 'list'> # a的类别为列表
<class ''> # b的类别为数组
  • 1
  • 2
  • 3
  • 4

接下来通过列表索引和数组索引来访问列表和数组中的元素,代码如下:

print(a[1]) 
print(b[1])
print(a[0:2]) 
print(b[0:2])
  • 1
  • 2
  • 3
  • 4

输出结果如下:

2  # 列表a索引调用的结果
2  # 数组b索引调用的结果
[1, 2]  # 列表a切片的结果,注意列表切片,左闭右开
[1 2]  # 数组b切片的结果,数组切片,也是左闭右开
  • 1
  • 2
  • 3
  • 4

从上面结果可以看到列表和数组有着相同的索引机制,唯一的区别好像就是数组里面是通过空格分隔元素,而列表用的是逗号。

2.1.2 Numpy数组与列表的区别

从上面的分析得知Numpy数组和列表很类似,那么为什么Python又要创建一个Numpy库呢?其原因很多,这里主要讲两点:
数组可以比较方便的进行一些数学运算,而列表则比较麻烦;
数组可以支持多维的数据,而列表通常只能储存一维的数据。
针对第一点,Numpy作为一个数据处理的库能非常好的支持一些数学运算,而列表则较为麻烦,以如下代码作为演示:

c = a * 2
d = b * 2
print(c)
print(d)
  • 1
  • 2
  • 3
  • 4

运行结果如下:

[1, 2, 3, 4, 1, 2, 3, 4]
[2 4 6 8]
  • 1
  • 2

可以看到同样通过乘法运算,列表是把元素复制了一遍,而数组则是进行了数学运算。
针对第二点,列表储存的是一维的数据,而数组则可以存储多维的数据。有的读者可能会疑惑,什么叫作一维的数据或者多维的数据呢?这里的一维和多维和立体几何比较类似,一维类似一条直线,多维则类似一个平面(二维)或者立体(三维)等。像列表则像一条直线,可以看作一维的,而平常用的Excel里的表格数据就可以看作一个二维的数据。以如下代码为例:

e = [[1,2], [3,4], [5,6]] # 列表里的元素为小列表
f = np.array([[1,2], [3,4], [5,6]]) # 创建二维数组的一种方式
  • 1
  • 2

列表e和数组f的打印结果如下所示:

[[1, 2], [3, 4], [5,6]]  # 这个是列表e的打印结果
[[1 2]
 [3 4]
 [5 6]]
  • 1
  • 2
  • 3
  • 4

可以看到列表虽然包含着三个小列表,但其还是一个一维的结构,而创建的二维数组则是一个三行两列的二维结构内容,这个也是之后学习pandas库的核心内容了,因为数据数据处理中经常用到二维数组,也即二维表格结构。

2.1.3 创建数组的几种方式

上面我们已经接触了创建数组的一种方式了,那就是通过(列表)的方式来创建列表,这边简单总结一下,代码如下:

# 创建一维数组
b = np.array([1, 2, 3, 4])
# 创建二维数组
f = np.array([[1,2], [3,4], [5,6]])
  • 1
  • 2
  • 3
  • 4

除此之外,还有一些常见的创建数组的方式,这里以一维数组为例,我们还可以采用()函数来产生一维数组,其中括号里可以选择1个或2个或3个参数,代码如下:

# 一个参数 参数值为终点,起点取默认值0,步长取默认值1
x = np.arange(5)
# 两个参数 第一个参数为起点,第二个参数为终点,步长取默认值1,左闭右开
y = np.arange(5,10)
# 三个参数 第一个参数为起点,第二个参数为终点,第三个参数为步长,左闭右开
z = np.arange(5, 10, 0.5)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

将结果打印输出如下:

[0 1 2 3 4]
[5 6 7 8 9] # 这里和列表切片一样也是左闭右开
[5.  5.5 6.  6.5 7.  7.5 8.  8.5 9.  9.5]
  • 1
  • 2
  • 3

我们还可以通过模块来创建随机一维数组,比如可以通过(3)来创建一个服从正太分布(均值为0,方差为1的分布)的3个随机数一维数组,代码如下:

a = np.random.randn(3)
  • 1

其打印输出结果如下所示:

[-0.02066164  0.42953796  1.17999329]
  • 1

如果把(3)换成(3),那生成的就是0-1之间的3个随机数,这个在之后2.3.1小节演示绘制散点图的时候会用到。
至于二维数组的创建与学习,可以利用一维数组中的()函数和reshape方法产生一个二维数组,比如将0到11个数转换成3行4列的二维数组,代码如下:

a = np.arange(12).reshape(3,4)
  • 1

其打印输出结果如下所示:

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
  • 1
  • 2
  • 3

这里再简单提一种随机二维数组的创建,代码如下:

a = np.random.randint(0, 10, (4, 4))
print(a)
  • 1
  • 2

其中()函数用来创建随机整数,括号里第一个元素0表示起始数,第二个元素10表示终止数,第三个元素(4, 4)则表示生成一个4行4列的二维数组,运行结果如下:

[[4 1 6 3]
 [3 0 4 8]
 [7 8 1 8]
 [4 6 3 6]]
  • 1
  • 2
  • 3
  • 4

2.2 pandas基础

pandas库是基于NumPy的一个开源Python库,被广泛用于快速分析数据,以及数据清洗和准备等工作。某种程度上可以把Pandas看作是Python版的Excel。如果是利用Anaconda安装的Python,那么Anaconda自带pandas库,无需单独安装。
相较于Numpy来说,Pandas更善于处理二维数据。Pandas主要有两种数据结构:Series和DataFrame。Series类似于通过Numpy产生的一维数组,不同的是Series对象不仅包含数值,还包含一组索引,其创建方式如下:

import pandas as pd
s1 = pd.Series(['丁一', '王二', '张三'])
  • 1
  • 2

生成结果如下所示,它也是一个一维数据结构,并且对于每个元素都有一个行索引可以用来定位,比如可以通过s1[1]来定位到第二个元素“王二”。

0    丁一
1    王二
2    张三
dtype: object
  • 1
  • 2
  • 3
  • 4

Series单独使用相对较少,pandas主要采用DataFrame数据结构。DataFrame是一种二维表格数据结构,直观一点的话可以将其看作一个Excel表格。

2.2.1 二维数据表格DataFrame的创建

有三种DataFrame常见的创建方法:通过列表创建、通过字典创建及通过二维数组创建。
(1) 通过列表创建DataFrame
首先是通过列表创建,这个和上一小节通过Numpy创建二维数组比较类似。引入pandas库方式通常为import pandas as pd,然后调用DataFrame功能创建二维数组。

import pandas as pd
a = pd.DataFrame([[1, 2], [3, 4], [5, 6]])
  • 1
  • 2

将a打印输出,运行结果如下:

   0  1
0  1  2
1  3  4
2  5  6
  • 1
  • 2
  • 3
  • 4

和之前通过Numpy生成的二维数组进行一个比较:

[[1 2]
 [3 4]
 [5 6]]
  • 1
  • 2
  • 3

可以看到通过pandas的DataFrame功能生成的二维数组更像我们在Excel中看到二维表格数据,它也有行索引和列索引,其中这里的索引序号都是从0开始的。
我们还可以自定义其列索引和行索引名称,代码如下:

a = pd.DataFrame([[1, 2], [3, 4], [5, 6]], columns=['date', 'score'], index=['A', 'B', 'C'])
  • 1

其中columns表示的就是列索引名称,index表示的就是行索引名称,输出结果如下:

    date  score
A     1      2
B     3      4
C     5      6
  • 1
  • 2
  • 3
  • 4

通过列表生成DataFrame还可以采用如下的方式,演示代码如下:

a = pd.DataFrame()  # 创建一个空DataFrame 
date = [1, 3, 5]
score = [2, 4, 6]
a['date'] = date
a['score'] = score
  • 1
  • 2
  • 3
  • 4
  • 5

注意要保证date列表和score列表的长度一致,否则会报错,其效果如下:

     date  score
0     1      2
1     3      4
2     5      6
  • 1
  • 2
  • 3
  • 4

该方法在第五章5.2.2小节汇总特征变量名称及特征重要性时就有应用。

(2) 通过字典创建DataFrame
除了通过列表创建DataFrame,还可以通过字典来创建DataFrame并可以自定义列索引,这里默认字典键为列索引,代码如下:

# 通过Pandas创建二维数组 - 字典法
b = pd.DataFrame({'a': [1, 3, 5], 'b': [2, 4, 6]}, index=['x', 'y', 'z'])
print(b)  # 在Jupyter Notebook编辑器中也可以直接输入b进行查看
  • 1
  • 2
  • 3

输出结果如下,可以看到列索引已经变成了这里字典里的键名了。

   a  b
x  1  2
y  3  4
z  5  6
  • 1
  • 2
  • 3
  • 4

如果想让字典键变成行索引,可以通过from_dict的方式来将字典转换成DataFrame,并同时设置orient参数为index,代码如下:

c = pd.DataFrame.from_dict({'a': [1, 3, 5], 'b': [2, 4, 6]}, orient="index")
print(c)
  • 1
  • 2

其中orient参数指定字典键对应的方向,默认值为columns,如果不设置成index的话,则还是默认字典键为列索引,输出结果如下,此时的字典键已经是行索引了。

   0  1  2
a  1  3  5
b  2  4  6
  • 1
  • 2
  • 3

除了通过from_dict()函数设置orient参数外,我们还可以通过DataFrame的.T属性来对列表进行转置,演示代码如下:

b = pd.DataFrame({'a': [1, 3, 5], 'b': [2, 4, 6]})
print(b)  # 打印原始表格
print(b.T)  # 打印转置后的表格
  • 1
  • 2
  • 3

结果如下,可以看到通过.T同样可以对表格进行转置,此外注意,如果想改变原来的表格结构,需要进行重新赋值,写成b = ,这样就会改变原来b的表格结构了。

   a  b
0  1  2
1  3  4
2  5  6
   0  1  2
a  1  3  5
b  2  4  6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(3) 通过二维数组创建
通过Numpy产生的二维数组,也可以创建DataFrame,这里以2.1.3小节里提到的二维数组为例生成一个3行4列的DataFrame,代码如下:

import numpy as np
d = pd.DataFrame(np.arange(12).reshape(3,4), index=[1, 2, 3], columns=['A', 'B', 'C', 'D'])
  • 1
  • 2

其打印输出结果如下所示:

   A  B   C   D
1  0  1   2   3
2  4  5   6   7
3  8  9  10  11
  • 1
  • 2
  • 3
  • 4

补充知识点:修改行索引或列索引名称
有时我们想要修改之前设置的行索引或列索引的名称,那么这时可以通过rename()函数进行实现,首先通过上面的知识点构造一个演示DataFrame,代码如下:

a = pd.DataFrame([[1, 2], [3, 4]], columns=['date', 'score'], index=['A', 'B'])
  • 1

此时表格a如下所示:

     date  score     
A      1      2
B      3      4
  • 1
  • 2
  • 3

如果想对索引进行重命名的话,rename()函数的使用方法如下:

a = a.rename(index={'A':'阿里', 'B':'腾讯'}, columns={'date':'日期','score':'分数'})
  • 1

这里通过rename之后并没有改变原表格结构,需要重新赋值给a才能改变原表格,或者在rename()中设置inplace参数为True,也能实现真正替换,代码如下:

a.rename(index={'A':'阿里', 'B':'腾讯'}, columns={'date':'日期','score':'分数'}, inplace=True)
  • 1

此时表格a如下所示:

     日期  分数   
阿里   1   2
腾讯   3   4
  • 1
  • 2
  • 3

通过values属性,也可以查看此时的index值,代码如下:

a.index.values
  • 1

打印结果如下,它是一个一维array格式的数组。

['阿里' '腾讯']
  • 1

如果想给行索引命名,也可以通过如下代码::

a.index.name = '公司'
  • 1

获取结果如下:
在这里插入图片描述

     日期	分数
公司		
阿里	1	2
腾讯	3	4
  • 1
  • 2
  • 3
  • 4

如果想把行索引变成某列的内容,可以使用set_index()函数,代码如下:

a = a.set_index('日期')
  • 1

此时的表格a如下所示:

    分数
日期	
1	 2
3	 4
  • 1
  • 2
  • 3
  • 4

和rename()函数一样,我们也可以设置inplace参数为True,这样就不用进行重新赋值:

a.set_index('日期', inplace=True)
  • 1

如果此时想把行索引换成数字索引,则可以使用reset_index()函数,代码如下:

a = a.reset_index()
  • 1

此时的表格a便变成了以数字作为行索引的表格了:

   日期	分数
0	1	2
1	3	4
  • 1
  • 2
  • 3

在这里插入图片描述
上面也可以直接 = [‘xxx’, ‘xxx’]

此外,如果想单纯修改列名的话,例如有个DataFrame的列名为“date”和“score”,则可以通过如下方式进行修改(此外如果想查看某一个DataFrame的列名,可以通过print()进行查看):
在这里插入图片描述

补充说明下,如果想修改列索引,也可以通过 = [‘xxx’, ‘xxx’]来快速进行设置,演示代码如下:

a = pd.DataFrame([[1, 2], [3, 4], [5, 6]], columns=['date', 'score'], index=['A', 'B', 'C'])
a .columns = ['日期', '分数'] 
  • 1
  • 2

2.2.2 Excel等文件的读取和写入(重要)

通过pandas,可以从多种数据文件中读取文件,并且可以将获得的数据导入到这些文件中。本节以Excel和CSV文件讲解一下如何进行文件的读取和写入。
(1) 文件读取
输入以下代码,用于读取Excel数据:

import pandas as pd
data = pd.read_excel('')  # data为DataFrame结构
  • 1
  • 2

这里的Excel文件后缀为xlsx,如果是2003版或再之前的Excel,其后缀则为xls。这里使用的文件路径是相对路径,也即代码所在的文件路径,也可以设置成绝对路径(相对路径和绝对路径的相关知识点参考本节补充知识点)。
通过打印data我们便可以查看此时的表格,或者我们可以打印()查看表格的前五行内容(如果写成head(10)则可以查看前10行数据),代码如下:

data.head()
  • 1

打印结果如下:
在这里插入图片描述
其中read_excel还可以设定参数,使用方式如下:

data = pd.read_excel('', sheet_name=0) 
  • 1

几个比较常见的参数如下:sheet_name:指定sheet表,可以输入sheet名称,也可以为数字(默认为0,也即第1个sheet);index_col:设置某一列设置为行索引。
除了可以读取Excel文件外,pandas还可以读取CSV文件。CSV文件也是存储数据的一种文件格式,和Excel文件相比,CSV文件本质是一个文本文件,它存储数据,但不包含格式,公式,宏等,所以其所占空间通常较小。CSV文件用逗号分隔一系列值,既可以通过Excel打开,也可以通过文本编辑器(记事本)打开。
输入以下代码,用于读取CSV文件:

data = pd.read_csv('')
  • 1

read_csv也可以指定参数,使用方式如下:

data = pd.read_csv('', delimiter=',', encoding='utf-8')
  • 1

其中delimiter参数指CSV文件中的分隔符号,默认为逗号; encoding则是设置编码方式,如果出现中文乱码,则需要设定为utf-8或者gbk,也可以通过设置index_col设置索引列。

(2) 文件写入
通过如下代码,可以将数据写入到Excel文件当中。

# 先生成一个DataFrame
data = pd.DataFrame([[1, 2], [3, 4], [5, 6]], columns=['A列','B列'])
# 将DataFrame导入到Excel当中
data.to_excel('data_new.xlsx')
  • 1
  • 2
  • 3
  • 4

运行之后将在代码所在的文件夹生成一个名为data_new的Excel文件,如下所示:
在这里插入图片描述
这里文件存储路径使用的也是相对路径,即保存在代码所在的文件夹,也可以根据需要写成绝对路径,文件相对路径和绝对路径见下面的补充知识。
在上表中,保存的Excel第一列还保留了索引信息,如果想将其删去,可以设置to_excel的参数index为False。to_excel的常见参数有如下一些:sheet_name:数据表名;index:True or False,默认为True保存索引信息,即输出文件的第一列为索引值,选择False的话则忽略索引信息;columns:选择所需要的列;encoding:编码方式。
例如要将数据表格导入到Excel文件中并忽略行索引信息,则代码如下:

data.to_excel('data_new.xlsx', index=False)
  • 1

通过类似的方式,可以将数据写入到CSV文件当中,代码如下:

data.to_csv('data_new.csv')
  • 1

和to_excel类似,to_csv也可以设置index、columns、encoding等参数。注意,如果在导出CSV文件事出现了中文乱码现象,且encoding参数设置成“utf-8”失效,则需要将encoding参数设置成“utf_8_sig”,代码如下:

data.to_csv('演示.csv', index=False, encoding="utf_8_sig")
  • 1

补充知识点:文件相对路径与绝对路径
相对路径
文件相对路径,即代码所在的文件夹,例如上面案例中写的data.to_excel(‘’)就是在代码所在的文件夹生成Excel文件。此外,如果写成data.to_excel(‘XX文件夹/’)则是表示在代码所在文件夹下的“XX文件夹”中生成Excel文件。
绝对路径
文件绝对路径,就是文件完整的路径名称,例如’E:\大数据分析\’就是绝对路径,不过因为在Python中反斜杠“\”经常有特殊含义,比如说“\n”表示换行,所以通常建议写绝对路径的时候写两个反斜杠取消可能存在的单个反斜杠的特殊含义,写成’E:\大数据分析\’。
除了用两个反斜杠来取消一个反斜杠的特殊意义外,还可以在文件路径的字符串前面加一个r,也可以取消单个反斜杠的特殊含义,代码如下:

data.to_excel('E:\\大数据分析\\')  # 绝对路径推荐写法1
data.to_excel(r'E:\大数据分析\')  # 绝对路径推荐写法2
data.to_excel('E:/大数据分析/') # 绝对路径推荐写法3
  • 1
  • 2
  • 3

2.2.3 数据读取与筛选(重要)

创建了DataFrame之后,需要对二维表格中的数据进行读取和筛选,本小节就来讲解下读取数据的多种方式以及如何对数据进行筛选。
首先创建一个三行三列的表格,行索引设定为r1、r2和r3,列索引设定为c1、c2和c3,以此为例来演示数据的读取与筛选,代码如下:

import pandas as pd
data = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=['r1', 'r2', 'r3'], columns=['c1', 'c2', 'c3'])
  • 1
  • 2

或者也可以通过2.2.1小节提到的以数组的方式来创建该DataFrame,这里以数字1为起点,数字10为终点(终点取不到)生成1到9九个数字,代码如下:

data = pd.DataFrame(np.arange(1,10).reshape(3,3), index=['r1', 'r2', 'r3'], columns=['c1', 'c2', 'c3'])
  • 1

两种方法打印输出结果如下所示:

    c1  c2  c3
r1   1   2   3
r2   4   5   6
r3   7   8   9
  • 1
  • 2
  • 3
  • 4

下面将讲解按照行列进行数据筛选、按照特定条件进行数据筛选、数据整体情况查看、数据运算、排序与删除等常用知识点。

1.按照行列进行数据筛选
(1) 按照列来选取数据
通过以下代码可以通过列来选取数据,这里先选取单列。

a = data['c1']
  • 1

其打印输出结果如下所示:

r1    1
r2    4
r3    7
Name: c1, dtype: int64
  • 1
  • 2
  • 3
  • 4

此时返回的结果里没有表头信息了,这是因为通过data[‘c1’]选取一列的时候返回的是一个一维序列结构的类,也可以通过如下代码返回一个二维的表格数据。

b = data[['c1']]
  • 1

其打印输出结果如下所示:

    c1
r1   1
r2   4
r3   7
  • 1
  • 2
  • 3
  • 4

若要选取多列,则需要在中括号[]中给个列表,比如要读取c1和c3列,则可以写为data[[‘c1’, ‘c3’]]。这里需要特别注意的是,必须是一个列表,而不能是data[‘c1’, ‘c3’],代码如下:

c = data[['c1', 'c3']]
  • 1

其打印输出结果如下所示:

    c1  c3
r1   1   3
r2   4   6
r3   7   9
  • 1
  • 2
  • 3
  • 4

(2) 按照行来选取数据
可以根据行的序号来进行选取,代码如下:

# 选取第23行的数据,注意序号从0开始,左闭右开
a = data[1:3] 
  • 1
  • 2

其打印输出结果如下所示:

    c1  c2  c3
r2   4   5   6
r3   7   8   9
  • 1
  • 2
  • 3

而pandas推荐使用iloc方法来根据行的序号进行行选取,它是根据行序号选取的另一种方法,pandas觉得这样更加直观,不会像data[1:3]可能会引起混淆,代码如下:

b = data.iloc[1:3]
  • 1

而且如果要选取单行的话,就必须得用iloc了,比如选择倒数第一行,代码如下:

c = data.iloc[-1]
  • 1

此时如果使用data[-1]则会报错,因为它可能会以为这个-1是列名,导致混淆报错。
除了通过行的序号选取外,还可以通过loc方法根据行的名称来进行选取,代码如下:

d = data.loc[['r2', 'r3']]
  • 1

有的时候如果行数很多,可以通过head()方法来选取前5行,代码如下:

e = data.head()
  • 1

这里因为只创建了3行数据,所以通过()会把全部数据都取到,如果只想取前两行的数据,可以写成(2)。

(3) 按照区块来选取
如果想选取某几行的某几列,则可以通过如下代码来实现,比如获得c1和c3列的前二行。

a = data[['c1', 'c3']][0:2]  # 也可写成data[0:2][['c1', 'c3']]
  • 1

其实就是把上面通过行和列选取数据的知识点整合了,运行结果如下:

    c1  c3
r1   1   3
r2   4   6
  • 1
  • 2
  • 3

在实战中,通常采用iloc和列选取混合的方式来选取特定的区块或值,代码如下:

b = data.iloc[0:2][['c1', 'c3']] 
  • 1

这样效果是一样的,而且逻辑清晰,代码不容易混淆:先通过iloc选取行,再通过列选取选取列,这也是pandas官方文档推荐的使用方法。
如果要选取单个的值,那么该方法就更有优势,比如选取c3列第一行的信息,就不能写成data[‘c3’][0]或data[0][‘c3’]了。下面的写法则比较清晰,iloc[0]先选取第一行,然后再选取c3列。

c = data.iloc[0]['c3']
  • 1

iloc[0, 1] # 表示第1行,第2列单元格

也可以通过iloc和loc方法来同时选择行和列,代码如下:

d = data.loc[['r1', 'r2'], ['c1', 'c3']]  
e = data.iloc[0:2, [0, 2]]  
  • 1
  • 2

注意loc方法使用字符串作为索引选择行和列,iloc方法使用数值作为索引选择行和列。有个记忆的方法,loc是location(定位、位置)的缩写,所以通过字符索引来定位,而iloc中多了一个字母i,而i又经常代表数值,所以iloc方法是用数值作为索引,其打印输出结果如下所示:

    c1  c3
r1   1   3
r2   4   6
  • 1
  • 2
  • 3

还有一个ix选择区域的方法,它也可以同时选择行和列,而且里面的内容不像loc或者iloc必须为字符索引或者数字索引,代码如下:

f = data.ix[0:2, ['c1', 'c3']]
  • 1

其逻辑及效果和[0:2][[‘c1’, ‘c3’]]一样,但ix目前已经不被pandas推荐了,所以之后需要用到区域选取,还是以[0:2][[‘c1’, ‘c3’]]的方式为主。
此外,再补充说一个知识点:iloc[0, 1]表示第1行,第2列单元格。

2.按照特定条件筛选
在方括号里还可以通过判断条件来过滤行,比如选取c1列数字大于1的行,代码如下:

a = data[data['c1'] > 1]
  • 1

其打印输出结果如下所示:

    c1  c2  c3  
r2   4   5   6   
r3   7   8   9  
  • 1
  • 2
  • 3

如果有多个筛选条件,则可以通过“&”符号(表示“且”)或“|”(表示“或”)连接,比如这边筛选,c1列数字大于1且c2列数字小于8的行,代码如下,注意要记得加判断条件两旁的小括号。

b = data[(data['c1'] > 1) & (data['c2'] < 8)]
  • 1

其打印输出结果如下所示:

     c1  c2  c3  
r2   4   5   6   
  • 1
  • 2

3.数据整体情况查看
通过表格的shape属性,可以查看表格整体的行数和列数,在表格数据量较大的时候能快速了解表格的行数和列数。

data.shape
  • 1

运行结果如下,其中第一个数字为表格行数,第二个数字为表格列数。

(3, 3)
  • 1

通过表格的describe()函数可以快速的查看表格每一列的数量、平均值、标准差、最小值、25分位数、50分位数、75分位数、最大值等信息,代码如下:

data.describe()
  • 1

运行效果如下:
在这里插入图片描述
通过value_counts()函数则可以快速的查看某一列都有什么数据,以及该数据出现的频次,代码如下:

data['c1'].value_counts()
  • 1

运行效果如下,可以看到c1列共有3种不同的数据,且每个出现的频次为1次。

7    1
1    1
4    1
Name: c1, dtype: int64
  • 1
  • 2
  • 3
  • 4

这些知识点将在14.3.2节查看电影评分数据时有应用。

4.数据运算、排序与删除
(1) 数据运算
从已有的列中,通过数据运算创造一个新的一列,代码如下:

data['c4'] = data['c3'] - data['c1']
data.head()
  • 1
  • 2

输出结果如下所示,其中()是输出表格的前5行,这里只有3行,所以全部显示。

    c1  c2  c3  c4
r1   1   2   3   2
r2   4   5   6   2
r3   7   8   9   2
  • 1
  • 2
  • 3
  • 4

(2) 数据排序
通过sort_values()可以根据列对数据进行排序,比如要对c2列进行降序排序,代码如下:

a = data.sort_values(by='c2', ascending=False)  
  • 1

其中by参数是根据那一列来进行排序,参数ascending为上升的意思,默认参数为True,设置为False的话,则表示降序排序,其打印输出结果如下所示。

   c1	c2	c3	c4
r3	7	8	9	2
r2	4	5	6	2
r1	1	2	3	2
  • 1
  • 2
  • 3
  • 4

其实如果是按列筛选,我们也可以直接写成如下代码,不用写“by=”,效果一样:

a = data.sort_values('c2', ascending=False)  
  • 1

如果要根据多列进行排序,则可以通过如下代码实现,其含义是根据c2列先进行降序排列,如果c2列有重复值,则根据c1列进行降序排列。
在这里插入图片描述
此外,通过sort_index()可以根据行索引进行排序,如按行索引进行升序排列,代码如下:

a = a.sort_index()
  • 1

运行代码后,则刚刚生成的a表格的行索引又变成r1,r2,r3的升序排列了。同样也可以设置ascending参数为False使其变成降序排序。
该知识点在第五章5.2.2小节最后按序查看变量特征重要性时便有应用。

(3) 数据删除
如果先要删除数据中指定的数据,就需要用到drop()函数。具体用法如下:

DataFrame.drop(index=None, columns=None, inplace=False)
  • 1

其常用的几个参数解释如下:index:指定要删除的行;columns:指定要删除的列;inplace:默认为inplace=False,该删除操作不改变原数据,而是返回一个执行删除操作后的新dataframe,如果选择inplace=True,则会直接在原数据上进行删除操作。
例如删除c1列的数据,代码如下:

a = data.drop(columns='c1')
  • 1

删除多列的数据,比如c1和c3列,可以通过列表的方式将所需删除的列声明,代码如下:

b = data.drop(columns=['c1', 'c3'])
  • 1

如果要删除行数据,比如删去第一行和第三行的数据,代码如下:

c = data.drop(index=['r1', 'r3'])
  • 1

注意这里要输入行索引的名称而不是数字序号,不过如果行索引名称本来就是数字,那么可以输入对应数字。上面删除数据后又赋值给新的变量不会改变原来表格data的结构,如果想改变原来表格的结构,可以令inplace参数为True,代码如下:

data.drop(index=['r1','r3'], inplace=True)
  • 1

该知识点在11.2节数据预处理重复值等相关内容处理时便有应用。

2.2.4 数据表拼接

pandas还提供了一些高级功能:数据合并与重塑,这为两个表格的合并拼接提供了极大的便利。主要包括merge、concat、append三种方法,下面以一个简单例子对它们进行简单的介绍和演示。
假设有如下两个DataFrame表格,需要对它们进行合并:

import pandas as pd
df1 = pd.DataFrame({'公司': ['万科', '阿里', '百度'], '分数': [90, 95, 85]})
df2 = pd.DataFrame({'公司': ['万科', '阿里', '京东'], '股价': [20, 180, 30]})
  • 1
  • 2
  • 3

其中df1和df2如下所示,这里的分数指的是分析师对该公司打出的评分。
在这里插入图片描述
(1) merge()函数
merge()函数根据一个或多个键将不同表格中的行连接起来,示例如下:

df3 = pd.merge(df1, df2)
  • 1

运行效果如下:
在这里插入图片描述
可以看到通过merge()函数直接选取**相同的列名(“公司”这一列)**进行合并,而且默认选取的是两种表共有的列内容(万科、阿里),有的时候如果相同的列名不止一个,可以通过on参数指定按照哪一列进行合并,代码如下:

df3 = pd.merge(df1, df2, on='公司')
  • 1

默认的合并其实是取交集(inner连接),也即取两表共有的内容,如果想取并集(outer连接),也即选取两表所有的内容,可以设置how参数,代码如下:

df3 = pd.merge(df1, df2, how='outer')
  • 1

运行效果如下,可以看到所有数据都有了,原来没有的内容则赋值为空值NaN。
在这里插入图片描述
如果想保留左表全部内容,而对右表不太在意的话,可以将how参数设置为left:

df3 = pd.merge(df1, df2, how='left')
  • 1

此时df3如下表所示,df1的内容(万科、阿里、百度)都保留完整了。
在这里插入图片描述
同理,如果想保留右表全部内容,而对左表不太在意的话,可以将how参数设置为right。
如果想根据行索引进行合并,可以通过设置left_index和right_index参数,代码如下:

df3 = pd.merge(df1, df2, left_index=True, right_index=True)
  • 1

此时df3如下表所示,两张表按照它们的行索引进行合并了。
在这里插入图片描述
注意在通过join()函数进行拼接的时候,两张表格中不能有名字相同的列名,如果存在的话,则需要设置lsuffix参数(左表同名列的后缀,suffix的中文翻译就是后缀的意思,l表示left)和rsuffix参数(右表同名列的后缀,这里的r表示right),没有相同列名的话,则可以直接写(df2),相对于merge()函数写法较为简洁一些。
实战中可以只记merge()函数的用法,这里讲解join()函数的目的是为了看到别人用join()函数的时候能够理解。该知识点在14.3.3小节进行数据表合并的时候便有应用。

(2) concat()函数
concat()函数是一种全连接(UNION ALL)方式,它不需要对齐,而是直接进行合并(即它不需要两表的某些列或者索引相同,只是把数据整合到一起)。所以concat没有"how"和"on"参数,而是通过“axis”指定连接的轴向。
默认情况下,axis=0,按行方向进行连接。

df3 = pd.concat([df1,df2], axis=0)
  • 1

结果如下:
在这里插入图片描述
此时行索引为原来两张表各自的索引,如果想重置索引,可以使用6.2.1小节讲过的reset_index()方法将索引重置,或者在concat()中设置ignore_index=True,忽略原有索引,按新数字索引进行排序。
如果想按列方向进行连接,可以设置axis参数为1。

df3 = pd.concat([df1,df2],axis=1)
  • 1

结果如下:
在这里插入图片描述
(3) append()函数(重要)
append()函数可以说concat()函数的简化版,效果和([df1,df2]) 类似,代码如下:

df3 = df1.append(df2)
  • 1

其效果和通过([df1,df2])产生的效果是一样的。
append()函数还有个常用的功能,和列表**.append()**一样,可用来新增元素,代码如下:

df3 = df1.append({'公司': '腾讯', '分数': '90'}, ignore_index=True)
  • 1

这里一定要设置ignore_index=True忽略原索引,否则会报错,生成的df3如下所示:
在这里插入图片描述
数据表拼接的相关知识点将在14.3.2节电影数据拼接时会有用到。
除了上面的知识点外,在11.2小节我们还会讲解如何通过pandas库处理重复值、缺失值及异常值,在14.3.3小节讲解数据透视表函数pivot_table()函数,以及在14.3小节的补充知识点讲解pandas库的groupby()函数相关知识点。

2.3 Matplotlib数据可视化基础

在Python中,有个非常出色的数据可视化库叫作Matplotlib,如果是通过Anaconda安装的Python,那么便已经自带该库,无需单独安装。本小节主要就来讲解下Matplotlib的基本用法及一些常见的小技巧。

2.3.1 基本图形绘制

在画图之前首先导入matplotlib库,其导入方式通常写作“import as plt”,as是对导入库的重命名以方便之后的使用,下面画图只需要调用plt的相应函数即可,如()为折线图,()为柱状图,()为散点图,()为饼状图,()为直方图等,这里以折线图、柱状图、散点图以及直方图为例讲解下图形的基本绘制方法。
(1) 折线图
通过()可以绘制折线图,代码如下:

import matplotlib.pyplot as plt
x = [1, 2, 3]
y = [2, 4, 6]
plt.plot(x, y)  # 绘制折线图
plt.show()  # 展示图形
  • 1
  • 2
  • 3
  • 4
  • 5

注意最后记得加上()来展示图形,运行结果如下图所示:
在这里插入图片描述
如果想让x和y之间有些数学关系,列表是不太容易进行数学运算的,这时候就可以通过2.1.2小节所讲的Numpy库引入一维数组进行数学运算,代码如下:

import numpy as np
import matplotlib.pyplot as plt

x1 = np.array([1, 2, 3])

# 第一条线:y = x + 1
y1 = x1 + 1
plt.plot(x1, y1) # 使用默认参数画图

# 第二条线:y = x*2
y2 = x1*2
# color设置颜色,linewidth设置线宽,单位像素,linestyle默认为实线,“--”表示虚线
plt.plot(x1, y2, color='red', linewidth=3, linestyle='--')

plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这里通过Numpy生成一个一维数组x1,并根据数组的可运算性生成了y1和y2,将两条线都画在一张图上,最终运行结果如下图所示:
在这里插入图片描述
(2) 柱状图
通过()可以绘制柱状图,代码如下:

import matplotlib.pyplot as plt
x = [1, 2, 3, 4, 5]
y = [5, 4, 3, 2, 1]
plt.bar(x, y)
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5

运行结果如下图所示:
在这里插入图片描述
(3) 散点图
通过()可以绘制散点图,代码如下:

import matplotlib.pyplot as plt
import numpy as np

x = np.random.rand(10)
y = np.random.rand(10)
plt.scatter(x, y)
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里通过2.1.3小节提到的(10)生成10个0-1之间的随机数,运行结果如下图所示:
在这里插入图片描述
(4) 直方图
通过()可以绘制频数或频率直方图,所谓直方图其实就是频数图或者频率图,横轴标为相关数据,纵坐标则为该数据出现的频数或者频率,演示代码如下:

import matplotlib.pyplot as plt
import numpy as np  

# 随机生成10000个服从正态分布的数据
data = np.random.randn(10000)

# 绘制直方图,bins为颗粒度,即直方图的长条形数目,edgecolor为长条形边框颜色
plt.hist(data, bins=40, edgecolor='black')

plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里通过2.1.3小节提到的(10000)生成10000个服从均值为0,标准差为1的正态分布数据,运行结果如下图所示,其中横轴表示随机生成的数据,纵轴则表示该数据出现的次数,也即频数。此外如果想显示为频率直方图的话,只需要设置参数density为1即可。
在这里插入图片描述
补充知识点:在pandas库中的快捷绘图技巧
以上是Matplotlib库经典图形的绘制技巧,其实对于pandas库中的数据表格,有更加方便的代码写法,不过其本质还是通过pandas库调用的Matplotlib库,演示代码如下:

# 这种写法只适合pandas中的DataFrame,不能直接用于Numpy的数组
import pandas as pd
df = pd.DataFrame(data)  # 将绘制直方图中的data数组转换成DataFrame()格式
df.hist(bins=40, edgecolor='black')
  • 1
  • 2
  • 3
  • 4

通过()的方式便可以快速地绘制和之前一样的直方图了,这里因为df是只有一列数,所以可以直接写df,如果df有多列数,那么绘制的时候就需要指明是哪一列需要绘制成直方图,写成df[‘列名’].hist(),该直方图的绘制技巧将在14.3.1节绘制电影评论次数直方图的时候用到。
此外,除了写()外,还可以通过下面这种pandas库里的通用绘图代码绘图:

df.plot(kind='hist')
  • 1

这里是通过设置kind参数为hist来绘制直方图,通过这种通用绘图代码,pandas库除了可以便捷的绘制直方图外,它还可以通过设置kind参数快捷地绘制其他图形,演示代码如下,首先通过2.2.1节的知识点创建一个二维DataFrame表格df。

import pandas as pd
df = pd.DataFrame([[8000, 6000], [7000, 5000], [6500, 4000]], columns=['人均收入', '人均支出'], index=['北京', '上海', '广州'])
  • 1
  • 2

演示二维表格df如下表所示:
在这里插入图片描述
此时可以通过pandas同时绘制折线图或者柱状图,代码如下:

df['人均收入'].plot(kind='line')  # kind=line绘制折线图,不设置则默认折线图
df['人均收入'].plot(kind='bar')  # kind=bar绘制柱状图
  • 1
  • 2

这里因为df有多列,所以先要通过df[‘列名’]的方式先选取需要绘图的列数据,最终效果如下,可以看到它将折线图和柱状图都快捷地绘制在图上了。此外如果直接写plot()函数,里面不传入kind参数的话,即写成df[‘人均收入’].plot()则默认绘制折线图。
在这里插入图片描述
此外设置kind参数为pie则可以绘制饼图,设置为box则可以绘制箱体图,效果如下:
在这里插入图片描述在这里插入图片描述

这里总结下pandas快捷绘图的技巧,其他设置kind参数技巧如下表所示,感兴趣的读者可以自己尝试将kind参数换成如下内容看下效果。
在这里插入图片描述
如果上面绘图过程中出现中文乱码,则可以在代码最前面加上如下三行代码解决中文乱码的问题,这三行代码是解决中文乱码的固定写法,这个之后也会讲解。

import matplotlib.pyplot as plt  # 下面这几行代码是解决中文乱码问题的
plt.rcParams['-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题
  • 1
  • 2
  • 3

2.3.2 数据可视化常见小技巧

下面主要讲解数据可视化过程中常用的一些小技巧,如添加文字说明、添加图例、设置双坐标轴、设置图片大小、设置中文以及如何绘制多图。
(1) 添加文字说明
通过(name)给图画添加标题;通过(),()用于添加x轴和y轴标签。

import matplotlib.pyplot as plt

x = [1, 2, 3]
y = [2, 4, 6]
plt.plot(x, y)
plt.title('TITLE')  # 添加标题
plt.xlabel('X')  # 添加X轴标签
plt.ylabel('Y')  # 添加Y轴标签
plt.show()  # 显示图片
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

运行结果如下图所示:
在这里插入图片描述
(2) 添加图例
通过()来添加图例,添加前需要设置好lable(标签)参数,代码如下:

import numpy as np
import matplotlib.pyplot as plt

# 第一条线, 设定标签lable为y = x + 1
x1 = np.array([1, 2, 3])
y1 = x1 + 1
plt.plot(x1, y1, label='y = x + 1') 

# 第二条线, 设定标签lable为y = x*2
y2 = x1*2
plt.plot(x1, y2, color='red', linestyle='--', label='y = x*2')

plt.legend(loc='upper left') # 图例位置设置为左上角
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如下图所示,已经绘制出两条直线,并在左上角添加了图例,如果想修改图例的位置,比如设置成右上角,可以将参数loc(位置location的缩写)修改成“upper right”,右下角则设置成“lower right”即可。
在这里插入图片描述
(3) 设置双坐标轴
上面的例子可以在一张图里画出两条线,但如果两条线的取值范围相差比较大,那么画出来的图效果便不太好,那么此时如何来画出两条y坐标轴呢?可以在画完第一个图之后,写如下一行代码即可设置双坐标轴。

plt.twinx()
  • 1

需要注意的是如果设置了双坐标轴,那么添加图例的时候,每画一次图就得添加一次,而不能在最后统一添加。这里以y = x和y = x^2为例,演示下如何设置双坐标轴,代码如下:

import numpy as np
import matplotlib.pyplot as plt

# 第一条线, 设定标签lable为y = x
x1 = np.array([10, 20, 30])
y1 = x1
plt.plot(x1, y1, color='red', linestyle='--', label='y = x')
plt.legend(loc='upper left')  # 该图图例设置在左上角

plt.twinx()  # 设置双坐标轴

# 第二条线, 设定标签lable为y = x^2
y2 = x1*x1
plt.plot(x1, y2, label='y = x^2') 
plt.legend(loc='upper right')  # 改图图例设置在右上角

plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

运行结果如下图所示,可以看到左右的y坐标轴数值相差很大。如果不设置双坐标轴,会导致y = x这条线被压缩的很平,影响图像显示效果。
在这里插入图片描述
(4) 设置图片大小
如果对默认图片大小不满意,可以通过如下代码可以设置图片大小:

plt.rcParams[''] = (8, 6)
  • 1

第一个元素代表长,第二个元素代表宽,这里的数字8和6代表的是800和600像素。

(5) 设置X轴角度
有的时候X轴可能因为内容较多,导致数据都挤在一块,这时候我们就可以设置x轴的角度来进行调节,代码如下,其中45表示45度,可以根据自己的需要来调整角度。

import matplotlib.pyplot as plt
plt.xticks(rotation=45)
  • 1
  • 2

(6) 中文显示问题
在使用matplotlib画图时,默认情况下是不支持中文显示的,通过如下代码可解决该问题。其中由于更改了字体导致显示不出负号,得将配署文件中 minus设为False。

import matplotlib.pyplot as plt
plt.rcParams['-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题
  • 1
  • 2
  • 3

这里的SimHei是黑体的英文翻译,如果想采用其他字体,可参考下面的字体英文对照表:
在这里插入图片描述
(7) 绘制多图
如下图所示,有时我们需要在一张画布上输出多个图形,在Matplotlib库中有当前的图形(figure)以及当前轴(axes)概念,其对应的就是当前画布以及当前子图,在一张画布(figure)上可以绘制多个子图(axes)。绘制多图通常采用subplot()函数或subplots()函数,
在这里插入图片描述
首先来讲解subplot()函数,如下图所示,它通常含有三个参数,子图的行数、列数以及第几个子图,例如subplot(221)表示的就是绘制2行2列的子图(共4个子图),并在第1个子图上进行绘图。
在这里插入图片描述
演示代码如下:

import matplotlib.pyplot as plt
# 绘制第一个子图:折线图
ax1 = plt.subplot(221)  
plt.plot([1, 2, 3], [2, 4, 6])  # 这里plt其实也可以换成ax1

# 绘制第二个子图:柱状图
ax2 = plt.subplot(222)  
plt.bar([1, 2, 3], [2, 4, 6])

# 绘制第三个子图:散点图
ax3 = plt.subplot(223)  
plt.scatter([1, 3, 5], [2, 4, 6])

# 绘制第四个子图:直方图
ax4 = plt.subplot(224)  
plt.hist([2, 2, 2, 3, 4])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这里正好复习一下2.3.1节基本图形的相关绘制方法,绘制结果如下图所示:
在这里插入图片描述
为了加强大家对画布(figure)和子图(axes)的理解,我们通过下面的代码来做一个简单演示:

plt.rcParams[''] = (8, 4) # 设置画布大小

plt.figure(1)  # 第一张画布
ax1 = plt.subplot(121)  # 第一张画布的第一个子图
plt.plot([1, 2, 3], [2, 4, 6])  # 这里的plt可以换成ax1

ax2 = plt.subplot(122)  # 第一张画布的第二个子图
plt.plot([2, 4, 6], [4, 8, 10])

plt.figure(2)  # 第二张画布
plt.plot([1, 2, 3], [4, 5, 6])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

其中第1行代码设置每一个画布大小为800*400像素;第3行代码(1)创建第1个画布,然后4-8行代码通过subplot()函数绘制两个子图;第10行代码(2)创建第2个画布,然后这个画布中只有一个子图。绘制结果如下图所示,两张画布其实就是绘制了两张图,其中第一张画布中有有两个子图,第二张画布中只有一个子图。
在这里插入图片描述
在使用subplot()函数的时候,每次在新的子图上画图时,都得调用subplot()函数,例如第四个子图就得写成ax4 = (224),那有没有什么办法,一次性就生成多个子图呢?这时候就可以用到subplots()函数,代码如下:

fig, axes =  plt.subplots(nrows=2, ncols=2)
ax1, ax2, ax3, ax4 = axes.flatten()
  • 1
  • 2

其中第1行代码subplots()函数中主要有2个参数,nrows表示行数,ncols表示列数,这里就是绘制2行2列的子图(共4个子图),它会返回两个内容:fig(画布)和axes(子图集合,以数组形式存储各个子图),也可以简写为:fig, axes = (2, 2);
第2行代码通过flatten()函数将子图集合展开,从而获得各个子图,这里因为是已知是4个子图,所以写成“ax1, ax2, ax3, ax4”分别代表4个子图,之后就可以在这四个子图中画图了,演示代码如下,这里采用subplots函数的简写方式,并设置图片尺寸figsize为1000*800像素。

fig, axes = plt.subplots(2, 2, figsize=(10, 8)) 
ax1, ax2, ax3, ax4 = axes.flatten()
ax1.plot([1, 2, 3], [2, 4, 6])  # 绘制第一个子图
ax2.bar([1, 2, 3], [2, 4, 6])  # 绘制第二个子图
ax3.scatter([1, 3, 5], [2, 4, 6])  # 绘制第三个子图
ax4.hist([2, 2, 2, 3, 4])  # 绘制第四个子图
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

最终绘制结果如下图所示:
在这里插入图片描述
此外,如果要在subplot()函数或者subplots()函数生成的子图中设置子图标题、X轴标签或Y轴标签,得通过set_title()函数、set_xlabel()函数、set_ylabel()函数进行设置,演示代码如下:

plt.rcParams['-serif'] = ['SimHei']  # 用来正常显示中文标签
fig, axes = plt.subplots(2, 2, figsize=(10, 8)) 
ax1, ax2, ax3, ax4 = axes.flatten()
ax1.plot([1, 2, 3], [2, 4, 6])  # 绘制第一个子图
ax1.set_title('子图1')
ax1.set_xlabel('日期')
ax1.set_ylabel('分数')
ax2.bar([1, 2, 3], [2, 4, 6])  # 绘制第二个子图
ax3.scatter([1, 3, 5], [2, 4, 6])  # 绘制第三个子图
ax4.hist([2, 2, 2, 3, 4])  # 绘制第四个子图
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里第一行设置中文字体为黑体(SimeHei)并使得中文不出现乱码,最终绘制结果如下图所示,可以看到第一张子图已经添加了标题和X轴坐标以及Y轴坐标等内容了。
在这里插入图片描述
总结来说,subpot在画布中绘图时,每次都要调用subplot指定位置,而subplots()可以一次生成多个子图 ,在调用时只需要调用生成子图的ax即可。

2.4 综合案例实战 - 股票数据读取与K线图绘制

库的更新

这个要更新的内容较多,可以在书上加一句话,让他扫描书前二维码,查看该勘误文档。

p66-p72的内容有些更新,主要原因是原来的mpl_finance库被废弃了,现在新的库叫作mplfinace,相应的代码也要做一些更新。
具体可以参考如下文档:/docs/pwwhYchCjkQt9YwK/ 《股票K线图绘制》,可复制链接后用石墨文档 App 或小程序打开

此外,如果tushare提示如下信息,如果还能用就用。如果不能用则参考官方文档切换为pro版本使用。
在这里插入图片描述
Tushare Pro的使用教程:
/docs/PGWdY6Q8xDKpvDXG/ 《Tushare Pro使用教程(一)》,可复制链接后用石墨文档 App 或小程序打开

本节我们将通过一个综合案例实战:股票数据读取与K线图绘制来复习并应用之前学习的pandas和Matplotlib库的相关知识点。我们将由易到难,先来进行初步尝试:股票数据读取与可视化,然后再进行综合实战:股票K线图绘制。

2.4.1 初步尝试 - 股票数据读取与可视化

这里我们用一个简单的股票数据读取与可视化的案例来巩固下之前学到的知识点。

1.股票数据库:Tushare库的安装与使用
首先推荐通过PIP安装法来安装可以调用股价数据的Tushare库(Tushare库官方地址为:/),以Windows系统为例,具体方法是:通过Win + R组合键调出运行框,输入cmd后回车,然后在弹出框中输入pip install tushare后按一下Enter回车键的方法来进行安装。如果在1.2.3节讲到的Jupyter Notebook编辑器中安装的话,只需要在代码框中输入如下代码然后运行该行代码框即可(注意是英文格式下的!):

!pip install tushare
  • 1

关于更多Tushare等股票相关库的用法可以参考本书8.2节“股票涨跌预测模型”相关知识点,这里主要为了复习pandas库及Matplotlib库的相关知识点。
我们只需要通过如下2行代码便可获取到股票基本数据:

import tushare as ts
df = ts.get_k_data('000002', start='2009-01-01', end='2019-01-01')
df.head()
  • 1
  • 2
  • 3

第1行引入tushare库并简写为ts;
第2行代码通过get_k_data()函数获取上市公司万科从2009-01-01到2019-01-01这10年的股票日线级别的数据,其中第一个参数’000002’表示的是股票代码(股票代码’000002’对应的股票是万科A),第二个参数start参数表示的是起始日期,第三个参数end参数表示的是结束日期。这里获得是一个2.2.1节提到的DataFrame的二维表格结构,将获取的结果赋予给变量df;该行代码也可以简写为:

df = ts.get_k_data('000002', '2009-01-01', '2019-01-01')
  • 1

第3行代码通过2.2.3节讲的()获取表格的前五行内容,如果不是Jupyter Notebook编辑器,则需要通过print()函数将其打印,写成print(()),最终效果如下图所示:
在这里插入图片描述
其中date为交易日期,open为开盘价、high为最高价、close为收盘价、low为最低价、volume为成交量、code为股票代码。
此时如果想要将股票数据获取到Excel文件中,则可以使用2.2.2节相关知识点,代码如下:

df.to_excel('股价数据.xlsx', index=False)
  • 1

其中设置index参数为False,即忽略原来的行索引,并且采用的是相对文件路径(相关知识点见2.2.2节补充知识点),最终将在代码所在文件夹生成一个Excel文件:股价数据.xlsx。

2.绘制股价走势图
已经有了股价数据后,我们可以通过可视化的方式将其展示出来,这里我们首先利用2.2.1节的补充知识点中的set_index()函数将日期设置为行索引,这样方便等会直接用pandas库进行绘图,代码如下:

df.set_index('date', inplace=True)
  • 1

此时的二维表格如下所示:
在这里插入图片描述
通过2.3.1节补充知识点中pandas绘图的相关知识点来进行图形绘制,代码如下。因为在pandas库中plot()函数默认绘制的是折线图,所以直接写plot()即可,不需要传入kind参数。此外在金融领域,通常用收盘价作为当天价格来绘制股价走势图,因此这里选择的是close这一列。

df['close'].plot()
  • 1

pandas库中plot()函数默认以行索引作为横轴坐标,我们之前设置了日期为行索引,所以最终绘制结果如下图所示:
在这里插入图片描述
如果想给图片加一个标题,在pandas库中使用可以在plot()可以在里面传入一个title参数,代码如下,注意因为标题是中文内容,所以要写2.3.2节最后讲到的两行代码防止中文乱码。

import matplotlib.pyplot as plt
plt.rcParams['-serif'] = ['SimHei']  # 用来正常显示中文标签
df['close'].plot(title='万科股价走势图')   
  • 1
  • 2
  • 3

获取结果如下图所示:
在这里插入图片描述
补充知识点:直接使用Matplotlib库画图的注意点
上面使用的是pandas库中的plot()函数,pandas库其实是集成了Matplotlib库的一些功能,如果有的读者想直接用Matplotlib库进行股价走势画图,可以采用如下代码:

# 通过Tushare库获取股价数据
import tushare as ts
df = ts.get_k_data('000002', start='2009-01-01', end='2019-01-01')

# 要注意的细节:调整日期格式使得横坐标显示清晰
from datetime import datetime
df['date'] = df['date'].apply(lambda x:datetime.strptime(x,'%Y-%m-%d'))

# 绘制折线图
import matplotlib.pyplot as plt
plt.plot(df['date'], df['close'])
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

绘制结果如下图所示:
在这里插入图片描述
其中需要注意的就是下面两行代码:

from datetime import datetime
df['date'] = df['date'].apply(lambda x:datetime.strptime(x,'%Y-%m-%d'))
  • 1
  • 2

因为df[‘date’]是string字符串类型的,如果直接用来画图的话,X轴坐标会出现很密集的现象,比较影响美观。所以这里通过()方法将其转化为timestamp时间戳格式,这样Matplotlib便会自动间隔显示日期。可以看到相比于使用pandas库绘制,直接使用Matplotlib库绘制稍微麻烦一点,得转换一下日期格式。

2.4.2 综合实战 - 股票K线图绘制

上一节我们学习了如何从Tushare库中调取股价基本数据以及如何绘制股价走势图,这一节我们来学习更加精彩的股票K线图绘制。
1.股票K线图基本知识
一个实际中的股票K线图如下图所示(这个是“贵州茅台”股票的日线级别的K线图):
在这里插入图片描述
没有接触过股票的读者可能会被里面的各个柱状图和折线图搞得一头雾水,而这些图形其实都是通过一些很基础的数据绘制而成,这一节便主要来科普下股票K线图的基本知识。
这些柱状图,通常称之为“K线图”,是由股票的四个价格来绘制的:开盘价(当天上午9点半开始交易时的价格)、收盘价(当天下午3点结束交易时的价格)、最高价(当天股价波动中的最高价)、最低价(当天股价波动中的最低价),简称“高、开、低、收”四个价格。
如下图所示,根据这四个价格便可以绘制出红色和绿色的K线图,因为形似蜡烛,因此也常被称之为蜡烛图。K线图分为两种,如果当天的收盘价高于开盘价,也就是说当天的价格上涨,则称之为阳线,通常绘制成红色;反之如果当天的收盘价低于开盘价,也就是说当天的价格下跌,则称之为阴线,通常绘制成绿色。补充说一句,在美国,反而是红色代表跌,绿色代表涨。
在这里插入图片描述
这里再解释下均线图,也就是那些折线图的绘制原理。均线分为5日均线(通常称之为MA5)、10日均线(通常称之为MA10)、20日均线(通常称之为MA20)等,其原理就是将股价的收盘价求均值,例如5日均线就是最近连续5个交易日收盘价之和的平均值,具体的计算公式如下,其中Close1为当天的收盘价,Close2为前一天的收盘价,其余依次类推。

MA5 = (Close1 + Close2 + Close3 + Close4 + Close5)/5
  • 1

把每个5日均线的值连成一条平滑的曲线就是5日均线图了,同理10日均线图和20日均线图也是类似的原理,这些均线图也就是我们在这一小节最开始看到图中的那些折线图。
了解了股票K线图的基本知识后,下面我们就来进行K线图的绘制工作。

2.绘制股票K线图
绘制股票K线图并不复杂,不过得做好一些准备工作,我们按部就班的来进行讲解。
(1) 安装绘制K线图的相关库:mpl_finance库
首先需要安装绘制K线图的相关库:mpl_finance库,其安装办法稍微麻烦一点,推荐通过PIP安装法安装,以Windows系统为例,具体方法是:通过Win + R组合键调出运行框,输入cmd后回车,然后在弹出框中输入如下内容,按一下Enter回车键进行安装:

pip install https:///matplotlib/mpl_finance/archive/
  • 1

如果是在在1.2.3节讲到的Jupyter Notebook中安装,则在pip前面加一个英文的感叹号“!”然后运行该代码块即可。

!pip install https:///matplotlib/mpl_finance/archive/  
  • 1

安装完mpl_finance库便可以调用其中的candlestick_ochl()函数来绘制K线图或者说蜡烛图了,在正式绘制之前,我们还需要做一些前期的数据准备工作。

(2) 引入绘图相关库
首先引入一些绘图需要用到的库,代码如下:

import tushare as ts
import matplotlib.pyplot as plt
import mpl_finance as mpf
import seaborn as sns
sns.set()
  • 1
  • 2
  • 3
  • 4
  • 5

第一个引入2.4.1节讲到的Tushare库,第二引入2.3.1节讲到的Matplotlib库在;第三个引入刚刚安装的mpl_finance库;第四个seaborn库是一个图表美化库,通过()即可激活,如果是通过1.2.1节Anaconda安装的Python,那么就自带该库了,无需额外安装。上面的代码直接拿去运行即可。

(3) 通过Tushare库获取股票基本数据
通过Tushare库获取股票代码为“000002”的股票“万科A”在2019-06-01至2019-09-30的股价数据,代码如下:

df = ts.get_k_data('000002','2019-06-01', '2019-09-30')
  • 1

获取结果如下图所示:
在这里插入图片描述
(4) 日期格式调整及表格转换
在进行K线图绘制之前,得做一点数据准备工作,这部分内容稍微有点复杂,不过实际应用的时候可以直接从附赠的源代码中拿过去使用,下面讲一下原理供感兴趣的读者学习。
因为绘制K线图的candlestick_ochl()函数只能接收特定格式的日期格式,以及数组格式的内容,所以我们需要将原来文本类型的日期格式调整一下,代码如下:

# 导入日期格式调整涉及的两个库
from matplotlib.pylab import date2num
import datetime

# 对tushare获取到的日期数据转换成candlestick_ohlc()函数可读取的数字格式
def date_to_num(dates):
    num_time = []
    for date in dates:
        date_time = datetime.datetime.strptime(date,'%Y-%m-%d')
        num_date = date2num(date_time)
        num_time.append(num_date)
    return num_time

# 将DataFrame转换为二维数组,并利用date_to_num()函数转换日期
df_arr = df.values  # 将DataFrame格式的数据,转换为array二维数组
df_arr[:,0] = date_to_num(df_arr[:,0])  # 将原来日期格式的日期换成数字格式
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

前3行代码首先引入装换日期格式的两个库;
然后5-12行代码定义转换日期格式的函数方便之后调用,函数内容主要是将文本格式的日期通过strptime()函数转换为时间戳格式的日期,然后通过date2num()函数将其转换为数字格式的日期;第15行代码通过values属性将原来DataFrame格式的二维表格转换为Numpy格式的二维数组,因为绘制K线图的candlestick_ochl()函数只能接收二维数组;
第16行代码通过5-12行定义的date_to_num()函数转换原来的日期格式,df_arr[:,0]中的“:”表示所有行,“0”表示第一列,因此其表示的就是二维数组的第一列,也就是日期“date”那一列。
感兴趣的读者也可以通过打印df_arr[0:5]展示转换格式后的前5行数,此时转换后的二维数据变成如下形式:

array([[737213.0, 26.81, 26.44, 27.02, 26.28, 317567.0, '000002'],
[737214.0, 26.47, 26.3, 26.54, 26.25, 203260.0, '000002'],
[737215.0, 26.64, 27.03, 27.28, 26.63, 576164.0, '000002'],
[737216.0, 27.01, 27.12, 27.29, 26.92, 333792.0, '000002'],
[737220.0, 27.29, 27.81, 28.05, 27.17, 527547.0, '000002']],
dtype=object)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以看到一开始的DataFrame格式的二维数据变成了二维数组格式,且日期那列内容由文本类型的日期转换为数字格式的日期,这样就方便之后使用绘制K线图的candlestick_ochl()函数。

(5) 绘制K线图
转换好数据格式后,K线图的绘制就比较简单了,通过candlestick_ochl()函数便能够轻松的绘制K线图了,代码如下:

fig, ax = plt.subplots(figsize=(15,6))
mpf.candlestick_ochl(ax, df_arr, width=0.6, colorup='r', colordown='g', alpha=1.0)
plt.grid(True)  # 绘制网格
ax.xaxis_date()  # 设置x轴的刻度为日期
  • 1
  • 2
  • 3
  • 4

其中第1行代码首先创建一个画布和子图(这里的子图只有一个),这里同时通过figsize参数设置图片像素大小为1500*600像素;第2行代码就是绘制K线图的核心代码candlestick_ochl()函数了,其主要参数含义如下所示:
在这里插入图片描述
这里的df_arr就是上一步骤数据处理后获得的股价历史数据;然后我们将colorup参数设置为“r”(红色red的缩写),也即当股票上涨的时候设置成红色;colordown参数设置为“g”(绿色green的缩写),也即当股票下跌的时候设置成绿色。
第3行代码通过(True)绘制网格,使得图表中有网格线;第四行代码通过xaxis_date()函数设置x轴的刻度为日期,也即将原来转换为数字格式的日期以常规日期格式显示。
最终绘图结果如下图所示:
在这里插入图片描述
(6) 绘制K线图及均线图
有了K线图之后,我们再来补上均线图,这里我们主要补上5日均线和10日均线图,首先我们通过如下代码构造5日均线和10日均线数据:

df['MA5'] = df['close'].rolling(5).mean()
df['MA10'] = df['close'].rolling(10).mean()
  • 1
  • 2

通过rolling()函数和mean()函数可以直接求均线数据,如果想求20日或者30日均值,只需要将rolling()函数中的数字换成20或30即可。感兴趣的读者可以通过(15)查看此时df的前15行数据,如下图所示:
在这里插入图片描述
可以看到5日均线MA5这一列前4行数据为空数据,这是因为5日均线数据取的是股价连续5个交易日的收盘价的均值,而前4天凑不到5天,因此也就没法算5日收盘价均值了,因此为空值;同理MA10列的前9行也为空值,到第10行才开始有数据。
有了5日均线和10日均线数据后,就可以将其绘制在图形中了,代码如下:

plt.rcParams['-serif'] = ['SimHei']  # 用来正常显示中文标签

fig, ax = plt.subplots(figsize=(15,6))

mpf.candlestick_ochl(ax, df_arr, width=0.6, colorup='r', colordown='g', alpha=1.0) 
plt.plot(df_arr[:,0], df['MA5'])  # 绘制5日均线
plt.plot(df_arr[:,0], df['MA10'])  # 绘制10日均线

plt.grid(True)  # 绘制网格

plt.title('万科A')  # 设置标题
plt.xlabel('日期')  # 设置X轴图例
plt.ylabel('价格')  # 设置Y轴图例

ax.xaxis_date () # 设置x轴的刻度为日期
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

其中第1行代码利用2.3.2节显示中文相关知识点来设置中文字体为黑体(SimHei)且不会出现中文乱码问题,这里通过如下两行代码绘制5日均线和10日均线:

plt.plot(df_arr[:,0], df['MA5'])  # 绘制5日均线
plt.plot(df_arr[:,0], df['MA10'])  # 绘制10日均线
  • 1
  • 2

其本质就是利用Matplotlib库的plot()函数绘制折线图,其中df_arr[:,0]就是之前步骤三中处理后的日期(“:”表示所有行,“0”表示第一列,因此其表示的就是二维数组的第一列,也就是日期“date”那一列)。
此时的图像如下图所示,可以看到已经将均线图绘制到图像上去了,这里我们还设置了图片标题和横轴及纵轴标签。
在这里插入图片描述
(7) 绘制股票K线图、均线图、成交量柱状图
在现实中,和股票K线图、均线图一同出现的还有每日成交量的的柱状图,我们利用2.3.2节绘制多图的知识点,即可通过如下代码在一张画布中绘制两个子图,包含K线图、均线图、成交量柱状图:

fig, axes = plt.subplots(2, 1, sharex=True, figsize=(15,8))
ax1, ax2 = axes.flatten()

# 绘制第一张子图:K线图和均线图
mpf.candlestick_ochl(ax1, df_arr, width=0.6, colorup = 'r', colordown = 'g', alpha=1.0)

ax1.plot(df_arr[:,0], df['MA5'])  # 绘制5日均线
ax1.plot(df_arr[:,0], df['MA10'])  # 绘制10日均线

ax1.set_title('万科A')  # 设置子图标题
ax1.set_ylabel('价格')  # 设置子图Y轴标签
ax1.grid(True)
ax1.xaxis_date()

# 绘制第二张子图:成交量图
ax2.bar(df_arr[:,0], df_arr[:,5])  # 绘制成交量柱状图
ax2.set_xlabel('日期')  # 设置子图X轴标签
ax2.set_ylabel('成交量')  # 设置子图Y轴标签
ax2.grid(True)
ax2.xaxis_date()  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

其中第1-2行代码利用2.3.2节绘制多图相关知识点先构造一个画布和两个子图,这里同时设置sharex参数为True,这样两张子图就可以共用一个坐标轴了;第4-13行绘制第一张子图,其中在子图中设置标题或者坐标轴标题得使用set_title()、set_ylabel()、set_xlabel()这样的函数;第15-20行绘制第二张子图:成交量图,其中df_arr[:,0]表示二维数组的第1列,也即日期那列,df_arr[:,5]表示二维数组的第6列,也即成交量那列数据,然后通过2.3.1节讲过的bar()函数绘制成柱状图。
最终绘制结果如下图所示:
在这里插入图片描述
我们可以和新浪财经网上的实际图像对比一下,如下图所示,发现通过Python绘制的K线图相关图片和网上的图片基本一致。
在这里插入图片描述
至此,数据分析的相关3大武器库已经给大家讲解完毕了,其实关于这三个库还有很多可以挖掘的知识点,由于篇幅有限,这里就不再赘述。这一章内容相对较多,读者朋友可以将这一章当作一个工具章,有需要的时候再返回看看需要用到的知识点。

/wuwei_201/article/details/105815728:新的教程1

/Wilburzzz/article/details/107792381 比较新的教程2。

pip install mplfinance
  • 1

代码如下:

import numpy as np
import pandas as pd
import tushare as ts
import mplfinance as mpf
import matplotlib.pyplot as plt
from pylab import mpl
from datetime import datetime


#pd.set_option()就是pycharm输出控制显示的设置
pd.set_option('expand_frame_repr', False)#True就是可以换行显示。设置成False的时候不允许换行
pd.set_option('display.max_columns', None)# 显示所有列
#pd.set_option('display.max_rows', None)# 显示所有行
pd.set_option('colheader_justify', 'centre')# 显示居中


pro = ts.pro_api('9d674d000f7c730dd3108701a1a1c534bf51bfb03a0ff169a9d11848')  # https:///user/token
mpl.rcParams['-serif'] = ['SimHei']  # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题
df = pro.daily(ts_code='', start_date='20200101', end_date='20200801')
#df.sort_values(by='trade_date',ascending=False)
data = df.loc[:, ['trade_date', 'open', 'close', 'high', 'low', 'vol']]  #:取所有行数据,后面取date列,open列等数据
data = data.rename(columns={'trade_date': 'Date', 'open': 'Open', 'close': 'Close', 'high': 'High', 'low': 'Low', 'vol': 'Volume'})  #更换列名,为后面函数变量做准备
data.set_index('Date', inplace=True)  #设置date列为索引,覆盖原来索引,这个时候索引还是 object 类型,就是字符串类型。
data.index = pd.DatetimeIndex(data.index)  #将object类型转化成 DateIndex 类型,pd.DatetimeIndex 是把某一列进行转换,同时把该列的数据设置为索引 index。
data = data.sort_index(ascending=True)  #将时间顺序升序,符合时间序列
mpf.plot(data, type='candle', mav=(5, 10, 20), volume=True, show_nontrading=False)
  • 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

2.5 课程相关资源

笔者获取方式:微信号获取

添加如下微信:huaxz001 。

笔者网站:

王宇韬相关课程可通过:
京东链接:[/Search?keyword=王宇韬],搜索“王宇韬”,在淘宝、当当也可购买。加入学习交流群,可以添加如下微信:huaxz001(请注明缘由)。
在这里插入图片描述

各类课程可在网易云、51CTO搜索王宇韬,进行查看。
在这里插入图片描述
在这里插入图片描述