数据分析之入门与实战

3,886 阅读33分钟

文章中所包含的数据:

链接:pan.baidu.com/s/1yP3_VTyk…

提取码:um6i

Numpy入门

数据科学领域5个常用的python库

Numpy

  • N维数组(矩阵),快速高效,矢量数学运算
  • 高效的Index,不需要循环
  • 开源免费跨平台,运行效率足以和C/Matlab媲美

Scipy

  • 依赖于Numpy
  • 专为科学和工程设计
  • 实现了多种常用科学计算:线性代数,傅里叶变换,信号和图像处理

Pandas

  • 结构化数据分析利器(依赖Numpy )
  • 提供了多种高级数据结构:Time-Series(时间索引),DataFrame, Panel
  • 强大的数据索引和处理能力

Matplotlib

  • Python 2D绘图领域使用最广泛的套件
  • 基本能取代Matlab的绘图功能(散点,曲线,柱形等)
  • 通过mplot3d可以绘制精美的3D图

Scikit-learn

  • 机器学习的Python模块
  • 建立在Scipy之上,提供了常用的机器学习算法∶聚类,回归
  • 简单易学的API接口

数学基础回顾之矩阵运算

定义

  • 矩阵∶矩形的数组,即二维数组。其中向量和标量都是矩阵的特例
  • 向量:是指1xn或者nx1的矩阵
  • 标量:1x1的矩阵
  • 数组:N维的数组,是矩阵的延伸

特殊矩阵

全0全1矩阵

[00 ...  000 ...  0............00 ...  0]\begin{bmatrix} 0 & 0 & ... & 0\\ 0 & 0 & ... & 0\\ ...&...&...&...\\ 0 & 0 & ... & 0 \end{bmatrix}

[11 ...  111 ...  1............11 ...  1]\begin{bmatrix} 1 & 1 & ... & 1\\ 1 & 1 & ... & 1\\ ...&...&...&...\\ 1 & 1 & ... & 1 \end{bmatrix}

单位矩阵

对角线上的元素全为1,其余元素为0

[10 ...  001 ...  0............00 ...  1]\begin{bmatrix} 1 & 0 & ... & 0\\ 0 & 1 & ... & 0\\ ...&...&...&...\\ 0 & 0 & ... & 1 \end{bmatrix}

矩阵的加减运算

  • 相加,减的两个矩阵必须要有相同的行和列
  • 行和列对应元素相加减

数组的乘法(点乘)

数组乘法(点乘)是对应元素之间的乘法

矩阵的乘法(叉乘)

设A为mxp的矩阵,B为pxn的矩阵,mxn的矩阵C为A与B的乘积,记为C=AB,其中矩阵C中的第i行第j列元素可以表示为∶

Array的创建与访问

创建array

通过列表转换为一维数组

import numpy as np
list1 = [1, 2, 3, 4]
array1 = np.array(list1)

或者

array3 = np.arange(1, 10)
print(array3)

上述函数和range()相似,都是指明始止点,如果需要指定步长可以在终止值后加上步长值

array3 = np.arange(1, 10, 2)
print(array3)

创建二维数组

import numpy as np
list1 = [1, 2, 3, 4]
list2 = [5, 6, 7, 8]
array1 = np.array([list1, list2])
print(type(array1))
print(array1)

创建全零矩阵

  • 一维全零矩阵
array4 = np.zeros(5)
print(array4)

  • 二维全零矩阵
array4 = np.zeros([2, 3])
print(array4)

创建单位矩阵

array5 = np.eye(5)
print(array5)

查询数组消息

  1. 数组的行和列
sh = array1.shape
print(sh)

代表array1是一个两行四列的数组

  1. 元素个数
s = array1.size
print(s)

返回的8代表array1元素个数为8

  1. 元素的数据类型
ty = array1.dtype
print(ty)

如果数组里面的元素数据类型不一致,选择精度最高的数据类型

array2 = np.array([[1.0, 2, 3], [4.0, 5, 6]])
print(array2.dtype)

访问数array

访问一维数组

数组的访问和列表的切片差不多

array3 = np.arange(1, 10)
print(array3[1:5])

访问二维数组

array2 = np.array([[1, 2, 3], [4, 5, 6]])
print(array2[0][2])

或者

array2 = np.array([[1, 2, 3], [4, 5, 6]])
print(array2[0, 2])

数组切片

array2 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(array2[:2, 1:])

数组与矩阵的运算

随机数组的创建

  • 随机生成一个长度为10的,符合正态分布的一维数组
np.random.randn(10)

  • 生成一个10以内的随机数
np.random.randint(10)

  • 随机生成多维数组(以2X3的数组为例)
np.random.randint(10, size=(2, 3))

  • 随机生成一个一维数组
np.random.randint(10, size=20)

  • 把一维数组转换为多维数组
np.random.randint(10, size=20).reshape(4, 5)

数组的运算

  • 数组相加
a = np.random.randint(10, size=20).reshape(4, 5)
b = np.random.randint(10, size=20).reshape(4, 5)
print(a)
print(b)
print(a + b)

数组相加的结果是对应的每个元素分别相加

  • 数组相减
a = np.random.randint(10, size=20).reshape(4, 5)
b = np.random.randint(10, size=20).reshape(4, 5)
print(a)
print(b)
print(a - b)

数组相减的结果是对应的每个元素分别相减

  • 数组相乘
a = np.random.randint(10, size=20).reshape(4, 5)
b = np.random.randint(10, size=20).reshape(4, 5)
print(a)
print(b)
print(a * b)

数组相乘的结果是对应的每个元素分别相乘

  • 数组相除
a = np.random.randint(10, size=20).reshape(4, 5)
b = np.random.randint(10, size=20).reshape(4, 5)
print(a)
print(b)
print(a / b)

一个注意点: 数组相除有时候会发出警报,原因是被除的数组(b)中含有0元素,当被除数为0时,结果中会自动填充一个值inf,以告诉我们该除法运算非法

矩阵的创建

矩阵的创建和数组差不多

np.mat([[1, 2, 3], [4, 5, 6]])

数组可以直接转化为一个矩阵

np.mat(a)

矩阵的运算

  • 矩阵相加
a = np.random.randint(10, size=20).reshape(4, 5)
b = np.random.randint(10, size=20).reshape(4, 5)
A = np.mat(a)
B = np.mat(b)
print(A)
print(B)
print(A+B)

矩阵相加的结果是对应的每个元素分别相加

  • 矩阵相减
a = np.random.randint(10, size=20).reshape(4, 5)
b = np.random.randint(10, size=20).reshape(4, 5)
A = np.mat(a)
B = np.mat(b)
print(A)
print(B)
print(A-B)

矩阵相减的结果是对应的每个元素分别相减

  • 矩阵相乘
a = np.random.randint(10, size=20).reshape(4, 5)
b = np.random.randint(10, size=20).reshape(4, 5)
A = np.mat(a)
B = np.mat(b)
print(A)
print(B)
print(A*B)

注意: 直接输入A*B会报错,矩阵的乘法必须保证A矩阵的行数等于B矩阵的列数

a = np.random.randint(10, size=20).reshape(4, 5)
b = np.random.randint(10, size=20).reshape(5, 4)
A = np.mat(a)
B = np.mat(b)
print(A)
print(B)
print(A*B)

Array常用函数

  • 元素去重
np.unique(a)

返回数组a中不重复的元素

  • 求数组每一列的和
sum(a)

  • 求数组每一行的和
sum(a[0])

  • 求数组每一列的和
sum(a[:,0])

  • 返回数组中的最大值
a.max()

  • 返回某一行/列的最大值
max(a[0])  # 行
max(a[:, 0])   # 列

pandas入门

pandas只要同于数据分析与数据处理

pandas.Series

创建Series

pandas里面自带了一个Series类

  • 利用列表创建Series
pd.Series([1, 2, 3, 4])

Series里面包含两部分,除了数据本身(右边列)外还包括了索引(左边列)

  • 利用数组创建Series
pd.Series(np.arange(10))

  • 利用字典创建Series
pd.Series({'1': 1, "2": 2, "3": 3})

以字典的key为索引,value为值创建Series

  • 指定索引和对应的值
pd.Series([1, 2, 3, 4], index=['A', 'B', 'C', 'D'])

特殊情况,当index的数量大于值的数量时

pd.Series(s4, index=['A', 'B', 'C', 'D', 'E'])

自动填充NaN值

查看Series元素

s1.values

查看Series的索引

s1.index

索引从0开始,在4结束(结束不包含),间隔为1

访问Series的元素

  • 访问某一行
s4['A']

  • 访问某个范围
s4[s4 > 2]

把Series转化为字典

s4.to_dict()

空值转化

  1. pd.isnull()

遇见空值返回True

  1. pd.notnull()

不为空值返回True

Series命名

s5.name = 'Series'
s5.index.name = 'Series name'

pandas.Dataframe

创建Dataframe

一个有趣的小方法,使用webbrowser库打开链接(www.tiobe.com/tiobe-index…

link = 'https://www.tiobe.com/tiobe-index/'
webbrowser.open(link)  # 打开链接

复制内容

读取粘贴板内容

df = pd.read_clipboard()

查看列名

df.columns

查看某一列的操作

df.列名

筛选某几列数据组成一个新的DataFrame

DataFrame(df, columns=['Sep2022', 'Sep2021', 'ProgrammingLanguage'])

深入理解DataFrame和Series

DataFrame和Series的关系

创建一个DataFrame,并查看某一列的数据类型

data = {'country': ['Belgium', 'Indis', 'Brazil'], 'Capital': ['Brussels', 'NewDelhi', 'Brasilia'], 'Population': [11190846, 1303171035, 207847528]}
s1 = pd.DataFrame(data)
print(type(s1['Capital']))

image.png

访问DataFrame某一行

对于DataFrame某一行,我们可以使用iterrows()函数

返回一个generator的数据类型,对于每一个generator我们都可以利用for循环进行遍历

for i in s1.iterrows():
    print(i)
    print(type(i))

每一行的数据都被封装为一个元组

对于DataFrame的行数据,每一行包含了两个元素,分别是index和数据

且数据部分也是由Series组成

通过拼接Series创建DataFrame

data = {'country': ['Belgium', 'Indis', 'Brazil'], 'Capital': ['Brussels', 'NewDelhi', 'Brasilia'], 'Population': [11190846, 1303171035, 207847528]}
df1 = pd.DataFrame(data)
s1 = pd.Series(data['country'])
s2 = pd.Series(data['Capital'])
s3 = pd.Series(data['Population'])
df2 = pd.DataFrame([s1, s2, s3])

对比df1

  • df2不存在列名
  • df2都数据颠倒了(行列颠倒)

解决办法

df2 = pd.DataFrame([s1, s2, s3], index=data.keys())
df2 = df2.T   # 转置

IO操作

FormatTypeDataDescriptionReaderWriter
textCSVread_csvto_csv
textJSONread_jsonto_json
textHTMLread_htmlto_html
textLocal clipboardread_clipboardto_clipboard
binaryMS Excelread_excelto_excel
binaryHDF5 Formatread_hdfto_hdf
binaryFeather Formatread_featherto_feather
binaryMsgpackread_msgpackto_msgpack
binaryStataread_statato_stata
binarySASread_sas
binaryPython Pickle Formatread_pickleto_pickle
SQLSQLread_sqlto_sql
SQLGoogle Big Queryread_gbqto_gbq

DataFrame的Selecting和indexing

生成DataFrame

(所需要用到的文件可以在网盘中自取)

显示某几行/列数据

  • 获取首尾

默认是5行

imdb.head()
imdb.tail()

  • 获取中间某几行/列
sub_df = imdb[['director_name', 'movie_title', 'imdb_score']]
sub_df.iloc[10:20, :]

iloc函数中,逗号前的参数(10:20)代表着从第10行到第20行(不包括20),后面的参数是对列的限制

  • 嵌套切片

对于切片获取到的DataFrame而言,再次切片时,行数仍然从0开始

  • 通过index获取
tmp_df.loc[15:17, :]

该切片方法是一个闭区间获取,前后范围均包含在切片范围内

tmp_df.loc[15:17, :'movie_title']

Series和DataFrame的Reindexing

reindex的作用是对Series或DataFrame对象创建一个适应新索引的新对象。

Series reindex

s1 = pd.Series([1, 2, 3, 4], index=['A', 'B', 'C', 'D'])
s1 = s1.reindex(index=['A', 'B', 'C', 'D', 'E'])

如果不想以NaN填充,可以用fill_value方法来设置

s1 = s1.reindex(index=['A', 'B', 'C', 'D', 'E'], fill_value=5.0)

也可以用ffill方法实现前向填充,也就是补充的索引如果没有对应的数值,则取前一个索引的值填充。

ffill或pad: 前向(或进位)填充

bfill或backfill: 后向(或进位)填充

DataFrame reindex

两者区别主要在于,DataFrame可以对index或columns使用reindex方法。

  • 对index
df1 = DataFrame(np.arange(9).reshape((3, 3)), index=['a', 'c', 'd'], columns=['Ohio', 'Texas', 'California'])
df2 = df1.reindex(['a', 'b', 'c', 'd'])

  • 对columns
df3 = df1.reindex(columns=states)

对DataFrame使用ffill方法

df4 = df1.reindex(index=['a', 'b', 'c', 'd'], columns=states).ffill()

谈一谈NaN

NaN是"not a number"的简称,表示 Pandas 中缺少的值。

创建一个NaN

np.nan

type(n)

NaN运算

NaN in Series

检测NaN

s1 = Series([1, 2, np.nan, 3, 4], index=['A', 'B', 'C', 'D', 'E'])

  • .isnull()用 True 表示具有 NaN 值的元素,用 False 表示非 NaN 值的元素

  • .notnull()与.isnull()相反

删除NaN

  • .dropna()用于删除NaN值

NaN in DataFrame

df1 = DataFrame([[1, 2, 3], [np.nan, 5, 6], [7, np.nan, 9], [np.nan, np.nan, np.nan]])

检测NaN

处理NaN

df = pd.DataFrame({"name": ['Alfred', 'Batman', 'Catwoman'],
                   "toy": [np.nan, 'Batmobile', 'Bullwhip'],
                   "born": [pd.NaT, pd.Timestamp("1940-04-25"),
                            pd.NaT]})

  1. 直接使用.dropna()

删除所有含有NaN的行和列

DataFrame.dropna( axis=0, how=any’, thresh=None, subset=None, inplace=False)
  1. axis参数确定是否删除包含缺失值的行或列
  • axis=0或axis='index’删除含有缺失值的行,
  • axis=1或axis='columns’删除含有缺失值的列,

  1. how参数当我们至少有一个NA时,确定是否从DataFrame中删除行或列
how='all'或者how='any'
  • how='all’时表示删除全是缺失值的行(列)
  • how='any’时表示删除只要含有缺失值的行(列)

  1. thresh=n表示保留至少含有n个非na数值的行

  1. subset定义要在哪些列中查找缺失值
df.dropna(subset=['name', 'born'])

  1. inplace表示直接在原DataFrame修改

多级的index

在pandas中可以为series和dataframe设置多个index,也就是说可以有多级index和column。这样可以对pandas的操作更加灵活。

  1. Series 多级index
  • 创建多级的index
s1 = Series(np.random.randn(6), index=[['1', '1', '1', '2', '2', '2'], ['a', 'b', 'c', 'a', 'b', 'c']])

  • 获取 index 为 1 的 series
s1['1']

  • 获取 column 为 a 的 series
s1[:, 'a']

  1. 多级的Series, 可以转化为一个 dataframe
  • 二级series 可以转化dataframe
s1.unstack()

  • dataframe 转化为 series
s2 = df1.unstack()

s2 = df1.T.unstack()

  • 创建 一个 多级的 dataframe
df = DataFrame(np.arange(16).reshape(4, 4), index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]], \
               columns=[['beijing', 'beijing', 'shanghai', 'shanghai'], [8, 9, 8, 9]]
               )

  • 访问 多级 dataframe 的元素

Mapping和Replace

  1. mapping

map()函数可以用于Series对象或DataFrame对象的一列,接收函数作为或字典对象作为参数,返回经过函数或字典映射处理后的值。

  • 创建一个DataFrame
df1 = DataFrame({"城市":["北京","上海","广州"], "人口":[1000,2000,1500]}, index=['A','B','C'])

  • 附加多一列数据'GDP'
df1['GDP'] = Series([1000, 2000, 1500], index=['A', 'B', 'C'])

  • map()函数使用
df1['GDP'] = df1['GDP'].map(lambda x: x + 1)

  1. replace
  • 生成0到9的Series

s1 = Series(np.arange(10))

  • 使用.replace()后并不会修改源数据
s1.replace([1, 2, 3], [10, 20, 30])

pandas玩转数据

Series和DataFrame的简单数学运算

Series运算

对于series而言,对于index相同的值,会自动对齐相加,对于未重叠的部分,会将他们展示并用NAN值填充(类似于数据库当中的外连接所不同的是用NAN值填充了)

s1 = Series([1, 2, 3], index=['A', 'B', 'C'])
s2 = Series([4, 5, 6, 7], index=['B', 'C', 'D', 'E'])
print(s1 + s2)

NaN加任何数都为NaN

DataFrame的运算

对于dataframe而言结果也是一致的,只是它的对齐对象需要是index和column都相同的部分,未重叠的部分会以NAN值填充。

df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'), index=['Ohio', 'Texas', 'Colorado'])
df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(df1 + df2)

  • df.add函数

以NAN值填充那些不重叠的部分可能不是我们想要的,我们想以两个DF当中原本的值去填充,这就更像是外连接了,这时就可以使用df.add,单纯的add是行不通的,需要参数fill_value=0,参数fill_value=0是将两者不重叠的部分用0填充,所以最后得到的结果就是,原本不重叠的部分以NAN值填充的部分以0进行填充,相加的结果是原本的df的值。

df1.add(df2)

df1.add(df2, fill_value=0)

  • 除了加法以外,还有以下算术方法,以r开头的函数没有什么不同只是会反转参数。
方法说明
add,radd用于加法(+)的方法
sub,rsub用于减法(-)的方法
div,rdiv用于除法(/) 的方法
floordiv,rfloordiv用于底除(//) 的方法
mul,rmul用于乘法(*)的方法
pow,rpow用于指数(**)的方法

dataframe和series之间的运算

frame = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series = frame.iloc[0]
print(frame - series)

  • 如同上面的dataframe之间的运算当计算时,发现有未重叠的部分的那么未重叠的部分将以NAN值填充,并显示。
series2 = pd.Series(range(3), index=['b', 'e', 'f'])

  • 假如要按列进行广播,则需要用算术函数并且传入轴

当我们从arr当中减去arr[0],每一行都会这样做,这种从上到下都会执行相同操作的方式叫做广播。

series3 = frame['b']
frame.add(series3,axis=0)

函数应用和映射

Numpy的元素级应用方法也可以应用于pandas对象。

frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'),index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(np.abs(frame))

pandas本身也有将函数应用于pandas对象的方法,applymap,apply,map,不同的是applymap是只能整个数组,apply是用于一个series或者整个dataframe,map是只能用于series级别

frame['b'].apply(lambda x: abs(x))

frame['b'].map(lambda x: abs(x))

frame.applymap(lambda x: abs(x))

apply还可以用在整个frame

frame.apply(lambda x: abs(x))

因此在实际使用过程当中推荐使用apply就可以了,因为他既可以用在frame也可以用在series当中。

Series和DataFrame排序

Series排序

  1. sort_values()
s1 = Series(np.random.randn(10))
s2 = s1.sort_values()

参数说明:

  • by:指定列名(axis=0或’index’)或索引值(axis=1或’columns’)

  • axis:若axis=0或’index’,则按照指定列中数据大小排序;若axis=1或’columns’,则按照指定索引中数据大小排序,默认axis=0

  • ascending:是否按指定列的数组升序排列,默认为True,即升序排列

  • inplace:是否用排序后的数据集替换原来的数据,默认为False,即不替换

  • na_position:{‘first’,‘last’},设定缺失值的显示位置

  1. .sort_index()
s1 = Series(np.random.randn(10))
s2 = s1.sort_index()

参数介绍:

  • axis:轴索引(排序的方向),0表示按index,1表示按columns
  • level:若不为None,则对指定索引级别的值进行排序
  • ascending:是否升序排列,默认为True,表示升序
  • inplace:默认为False,表示对数据表进行排序,不创建新的实例
  • kind:选择排序算法

DataFrame排序

对于DataFrame而言,直接使用.sort_values()函数会报错,需要传入排序的列

重命名DataFrame的index

  1. 通过修改Series重命名
df1 = DataFrame(np.arange(9).reshape(3, 3), index=['BJ', 'SH', 'GZ'], columns=['A', 'B', 'C'])
df1.index = Series(['bj', 'sh', 'gz'])

  1. map函数
df1 = DataFrame(np.arange(9).reshape(3, 3), index=['bj', 'sh', 'gz'], columns=['A', 'B', 'C'])
df1.index.map(str.upper)

str.upper:把字符串中的字母转化为大写

str.lower:把字符串中的字母转化为小写

map函数是生成一个Index对象,而不是永久改变df1的值

想要永久改变df1的Index值,需要同时运用方法1和2

df1.index = df1.index.map(str.upper)

  1. rename函数
df1.rename(index=str.lower)

该函数用于产生一个修改后的DataFrame对象,不是直接修改df

  1. 通过字典进行个别行/列修改
df1.rename(index={'BJ': 'beijing'}, columns={'A': 'a'})

DataFrame的merge操作

merge操作类似于数据库当中两张表的join,可以通过一个或者多个key将多个dataframe链接起来。

df1 = pd.DataFrame({'id': [1, 2, 3, 3, 5, 7, 6], 'age': range(7)})
df2 = pd.DataFrame({'id': [1, 2, 4, 4, 5, 6, 7], 'score': range(7)})
  1. 通过id对两个DataFrame进行连接
pd.merge(df1, df2)

没有指定根据哪一列完成关联时,pandas会自动寻找两个DataFrame的名称相同列来进行关联。如果遇见需要指定列名的情况,只需要传入on这个参数即可。

pd.merge(df1, df2, on='id')

根据多列关联,也可以传入一个数组

假如两个DataFrame当中的列名不一致,我们需要用left_on指定左表用来join的列名,用right_on指定右表用来join的列名

df1 = pd.DataFrame({'id': [1, 2, 3, 3, 5, 7, 6], 'age': range(7)})
df2 = pd.DataFrame({'number': [1, 2, 4, 4, 5, 6, 7], 'score': range(7)})
pd.merge(df1, df2, left_on='id', right_on='number')

问题:

观察一下上面的结果会发现关联之后的数据条数变少了

原因:

默认的方式是inner join,也就是两张表当中都存在的数据才会被保留。如果是left join,那边左边当中所有的数据都会保留,关联不上的列置为None,同理,如果是right join,则右表全部保留,outer join则会全部保留(可联想MySQL中相关的操作)

  1. 左表保留
pd.merge(df1, df2, left_on='id', right_on='number', how='left')

  1. 其它参数介绍
  • left: 拼接的左侧DataFrame对象
  • right: 拼接的右侧DataFrame对象
  • on: 要加入的列或索引级别名称。 必须在左侧和右侧DataFrame对象中找到。 如果未传递且left_index和right_index为False,则DataFrame中的列的交集将被推断为连接键。
  • left_on:左侧DataFrame中的列或索引级别用作键。 可以是列名,索引级名称,也可以是长度等于DataFrame长度的数组。
  • right_on: 左侧DataFrame中的列或索引级别用作键。 可以是列名,索引级名称,也可以是长度等于DataFrame长度的数组。
  • left_index: 如果为True,则使用左侧DataFrame中的索引(行标签)作为其连接键。 对于具有MultiIndex(分层)的DataFrame,级别数必须与右侧DataFrame中的连接键数相匹配。
  • right_index: 与left_index功能相似。
  • how: One of ‘left’, ‘right’, ‘outer’, ‘inner’. 默认inner。inner是取交集,outer取并集。比如left:[‘A’,‘B’,‘C’];right[’'A,‘C’,‘D’];inner取交集的话,left中出现的A会和right中出现的买一个A进行匹配拼接,如果没有是B,在right中没有匹配到,则会丢失。'outer’取并集,出现的A会进行一一匹配,没有同时出现的会将缺失的部分添加缺失值。
  • sort: 按字典顺序通过连接键对结果DataFrame进行排序。 默认为True,设置为False将在很多情况下显着提高性能。
  • suffixes: 用于重叠列的字符串后缀元组。 默认为(‘x’,’ y’)。
  • copy: 始终从传递的DataFrame对象复制数据(默认为True),即使不需要重建索引也是如此。
  • indicator:将一列添加到名为_merge的输出DataFrame,其中包含有关每行源的信息。 _merge是分类类型,并且对于其合并键仅出现在“左”DataFrame中的观察值,取得值为left_only,对于其合并键仅出现在“右”DataFrame中的观察值为right_only,并且如果在两者中都找到观察点的合并键,则为left_only。

Concatenate和Combine

矩阵中的中的Concatenate和Combine

创建两个33的矩阵(arr1与arr2)并把两个矩阵连接起来,生成一个36的矩阵

arr1 = np.arange(9).reshape(3, 3)
arr2 = np.arange(9).reshape(3, 3)
np.concatenate([arr1, arr2])

横着连接,把arr2的数据放到arr1后面

np.concatenate([arr1, arr2], axis=1)

Series 中的Concatenate和Combine

Series 中的Concatenate

创建两个Series(s1和s2),连接两个Series,直接把s2拼接到s1下方

s1 = Series([1, 2, 3], index=['X', 'Y', 'Z'])
s2 = Series([4, 5], index=['A', 'B'])
pd.concat([s1, s2])

左右连接两个Series

pd.concat([s1, s2], axis=1)

Series中的Combine

s1 = Series([2, np.nan, 4, np.nan], index=['A', 'B', 'C', 'D'])
s2 = Series([1, 2, 3, 4], index=['A', 'B', 'C', 'D'])
s1.combine_first(s2)

combine_first会用s2里面的values填充s1里面没有的值

DataFrame中的Concatenate和Combine

DataFrame中的Concatenate

df1 = DataFrame(np.random.randn(4, 3), columns=['X', 'Y', 'Z'])
df2 = DataFrame(np.random.randn(3, 3), columns=['X', 'Y', 'A'])
pd.concat([df1, df2])

concat后会连接在一起,把没有的值使用NaN进行填充

DataFrame中的Combine

df1 = DataFrame({'X': [1, np.nan, 3, np.nan], 'Y': [5, np.nan, 7, np.nan], 'Z': [9, np.nan, 11, np.nan]})
df2 = DataFrame({'Z': [np.nan, 10, np.nan, 12], 'A': [1, 2, 3, 4], })
df1.combine_first(df2)

DataFrame的combine_first的结果和Series的结果是一样的,A这一列没变化,X和Y并没有被填充因为df2里面没有,Z这一列为两个DataFrame相互填充的结果(df1的‘Z’列偶数行为NaN,df2的‘Z’列奇数行为NaN)

通过apply进行数据预处理

(所需要用到的文件可以在网盘中自取)

  1. 读取数据
df = pd.read_csv()

  1. 添加列数据
s1 = Series(['a']*7978)
df['A'] = s1

  1. 使用apply对数据进行处理
  • 把字母变成大写
df['A'] = df['A'].apply(str.upper) 

  • 去掉头尾的空格,以中间的空格分隔
df['data'][0].strip().split(' ')

  • 用','分隔
df['data'][0].strip().split(',') 

  • 通过apply使用函数分隔
df = pd.read_csv()
def foo(line):
    items = line.strip().split(' ')
    return Series([items[1], items[3], items[5]])
df_tmp = df['data'].apply(foo)
df_tmp = df_tmp.rename(columns={0: "Symbol", 1: "Seqno", 2: "Price"})

  • 加入df_new中没有,df_tmp存在的属性列
df_new = df.combine_first(df_tmp)

  • 删除df_new中的'data'属性列
del df_new['data']

通过去重进行数据清洗

(所需要用到的文件可以在网盘中自取)

  1. 添加数据

  1. 删除无用数据
del df['Unnamed: 0']

  1. 查看非重复的数据
df['Seqno'].unique()

返回一个不含重复值列表

  1. 检测是否与前边重复
df['Seqno'].duplicated()

  1. 删除重复数据
df['Seqno'].drop_duplicates()

  1. 其它参数展示
  • subset: 列名,可选,默认为None

  • keep: {‘first’, ‘last’, False}, 默认值 ‘first’

    • first: 保留第一次出现的重复行,删除后面的重复行。
    • last: 删除重复项,除了最后一次出现。
    • False: 删除所有重复项。
  • inplace:布尔值,默认为False,是否直接在原数据上删除重复项或删除重复项后返回副本。(inplace=True表示直接在原来的DataFrame上删除重复项,而默认值False表示生成一个副本。)

时间序列操作基础

  1. 创建时间列表
date_list = [datetime(2022, 1, 1), datetime(2022, 1, 2), datetime(2022, 1, 3), datetime(2022, 1, 4),datetime(2022, 1, 5)]

  1. 创建DataFrame
s1 = Series(np.random.rand(5), index=date_list)

  1. 利用时间序列筛选数据
s1[datetime(2022, 1, 2)]

利用datetime的其它格式筛选也能达到一样的目的

s1['2022-1-2']

s1['20220102']

根据年/月份筛选

date_list = [datetime(2021, 1, 1), datetime(2021, 2, 2), datetime(2022, 1, 3), datetime(2022, 2, 4),             datetime(2022, 1, 5)]
s1 = Series(np.random.rand(5), index=date_list)
s1['2022-01']
s1['2022']

  1. date_range函数

参数:

  • periods:固定时期,取值为整数或None
  • freq:日期偏移量,取值为string或DateOffset,默认为'D'
  • normalize:若参数为True表示将start、end参数值正则化到午夜时间戳
  • name:生成时间索引对象的名称,取值为string或None
  • closed:可以理解成在closed=None情况下返回的结果中,若closed=‘left’表示在返回的结果基础上,再取左开右闭的结果,若closed='right'表示在返回的结果基础上,再取做闭右开的结果
pd.date_range(start='2022-01-01', periods=30)

时间序列数据的采样

  1. 生成时间序列
t_range = pd.date_range(start='2022-01-01', end='2022-12-31')

  1. 创建Series
s1 = Series(np.random.randn(len(t_range)), index=t_range)

  1. 传统采样和采样方法的改进
  • 传统采样方法(求月数据的平均值)
s1['2022-06'].mean()

传统采样一次只能获取到某一个月的信息

  • 改进---resample函数
DataFrame.resample(rule, axis=0, closed=None, label=None, convention='start', kind=None, loffset=None, base=None, on=None, level=None, origin='start_day', offset=None) [source]

参数说明:

  • rule : eDateOffset, Timedelta 或 str表示目标转换的偏移字符串或对象。

  • axis :{0 或‘index’, 1 或 ‘columns’}, 默认为 0向上采样或向下采样使用哪一个轴。对于级数,默认值为0,即沿着行。必须是DatetimeIndex, TimedeltaIndex或PeriodIndex。

  • closed : {‘right’, ‘left’}, 默认为Nonebin区间的哪一边是关闭的。除了‘M’、‘A’、‘Q’、‘BM’、‘BA’、‘BQ’和‘W’之外,所有频率偏移的默认值都是‘left’,它们的默认值都是‘right’。

  • label : {‘right’, ‘left’}, 默认为 None用哪边标签来标记bucket。除了‘M’、‘A’、‘Q’、‘BM’、‘BA’、‘BQ’和‘W’之外,所有频率偏移的默认值都是‘left’,它们的默认值都是‘right’。

  • convention : {'start', 'end', 's', 'e'}, 默认为 'start'仅对于PeriodIndex,控制是使用规则的开始还是结束。

  • kind : {‘timestamp’, ‘period’}, 可选, 默认为 None传递'timestamp'将结果索引转换为DateTimeIndex,或'period'将其转换为PeriodIndex。默认情况下,保留输入表示形式。

  • loffset : timedelta, 默认为None调整重新采样的时间标签。自1.1.0版本以来已弃用 : 您应该将loffset添加到df中。重新取样后的索引。见下文。

  • base : int, 默认为0,对于平均细分1天的频率,聚合间隔的“origin”。例如,对于“5min”频率,基数可以从0到4。默认值为0。自1.1.0版本以来已弃用:您应该使用的新参数是'offset'或'origin'。

  • on : str, 可选自1.1.0版本以来已弃用:您应该使用的新参数是'offset'或'origin'。

  • level : str 或 int, 可选用于重采样的多索引、级别(名称或数字)。级别必须与日期时间类似。

  • origin : {‘epoch’, ‘start’, ‘start_day’}, Timestamp 或 str, 默认为 ‘start_day’

  • 调整分组的时间戳。原始时区必须与索引的时区匹配。

  • 如果不使用时间戳,也支持以下值:

      1. “epoch”:起源是1970年01月01日
      1. 'start ': origin是timeseries的第一个值
      1. “start_day”:起源是timeseries午夜的第一天

新版本1.1.0。

  • offset : Timedelta 或 str, 默认为 None加到原点的偏移时间。

  1. 对月数据求平均
s1.resample('M').mean()

  1. 将数据往前填充(按小时填充)
s1.resample('H').ffill()

  1. 往后填充
s1.resample('H').bfill()

数据分箱技术

对成绩数据进行分箱

score_list = np.random.randint(25, 100, size=20)  # 随机生成20个在区间25~100中的数值
bins = [0, 59, 70, 80, 100]   # 设置分箱等级
pd.cut(score_list, bins=bins)

  • pd.cut函数

    • pd.cut( x, bins, right=True, labels=None, retbins=False, precision=3, include_lowest=False, duplicates='raise', )
      
    • x : 一维数组

    • bins :整数,标量序列或者间隔索引,是进行分组的依据,

    • 如果填入整数n,则表示将x中的数值分成等宽的n份(即每一组内的最大值与最小值之差约相等);

      • 如果是标量序列,序列中的数值表示用来分档的分界值
      • 如果是间隔索引,“ bins”的间隔索引必须不重叠
    • right :布尔值,默认为True表示包含最右侧的数值

      • 当“ right = True”(默认值)时,则“ bins”=[1、2、3、4]表示(1,2],(2,3],(3,4]
      • 当bins是一个间隔索引时,该参数被忽略。
    • labels : 数组或布尔值,可选.指定分箱的标签

      • 如果是数组,长度要与分箱个数一致,比如“ bins”=[1、2、3、4]表示(1,2],(2,3],(3,4]一共3个区间,则labels的长度也就是标签的个数也要是3
      • 如果为False,则仅返回分箱的整数指示符,即x中的数据在第几个箱子里
      • 当bins是间隔索引时,将忽略此参数
    • retbins: 是否显示分箱的分界值。默认为False,当bins取整数时可以设置retbins=True以显示分界值,得到划分后的区间

    • precision:整数,默认3,存储和显示分箱标签的精度。

    • include_lowest:布尔值,表示区间的左边是开还是闭,默认为false,也就是不包含区间左边。

    • duplicates:如果分箱临界值不唯一,则引发ValueError或丢弃非唯一

对分箱结果进行统计

pd.value_counts()

创建学生成绩DataFrame

df = DataFrame()
df['score'] = score_list
df['student'] = [pd.util.testing.rands(3) for i in range(20)]  # 随机生成长度为3的字符串

把分箱结果插入DataFrame中

df['categories'] = pd.cut(df['score'], bins)

格式化分箱结果

df['categories'] = pd.cut(df['score'], bins, labels=['Low', 'OK', 'Good', 'Greate'])

数据分组技术GroupBy

该分组技术与MySQL的分组类似

(所需要用到的文件可以在网盘中自取)

DataFrame.groupby(by=None, axis=0,level=None,as_index=True, sort=True, group_keys=True, squeeze=NoDefault.no_default, observed=False, dropna=True)
  • by:用于确定 groupby 的组。 如果 by 是一个函数,它会在对象索引的每个值上调用。 如果传递了 dict 或 Series,则 Series 或 dict VALUES 将用于确定组(Series 的值首先对齐;参见 .align() 方法)。 如果传递了长度等于所选轴的列表或 ndarray,则按原样使用这些值来确定组。 一个标签或标签列表可以通过 self 中的列传递给 group。 请注意,元组被解释为(单个)键。
  • axis:沿行 (0) 或列 (1) 拆分。
  • level:如果轴是MultiIndex(层次化),则按一个或多个特定级别进行分组。
  • as_index:对于聚合输出,返回具有组标签作为索引的对象。仅与DataFrame输入相关。as index=False是有效的sql风格的分组输出。
  • sort:对组键进行排序。 关闭此功能可获得更好的性能。 请注意,这不会影响每组内的观察顺序。 Groupby 保留每个组内的行顺序。
  • group_keys:当调用apply时,将组键添加到index以识别片段。
  • squeeze:如果可能,降低返回类型的维数,否则返回一致的类型。
  • observed:这仅适用于任何 groupers 是分类的。 如果为真:仅显示分类分组的观察值。 如果为 False:显示分类分组的所有值。
  • dropna:如果为 True,并且组键包含 NA 值,则 NA 值连同行/列将被删除。 如果为 False,NA 值也将被视为组中的键。
  1. 通过城市进行分组
df.groupby(df['city'])

返回一个DataFrameGroupBy对象

  1. 查看分组结果
.groups

返回一个字典类型的数据,字典的键key是分组的值,字典的值value是该组里面每一个元素的索引

  1. 查看某一分组数据
g.get_group

返回一个DataFrame

  1. 查看每一个分组的数据情况
g.方法名

  1. 分组原理

image.png

  1. 把分组结果转化为list
list(g)

数据聚合技术

普通聚合

g.describe()

分组运算方法

pandas提供了多种分组运算方法,包括aggregate、apply和transform,它们的用法各有不同,适用于不同的场景

  1. aggregate方法

前面用到的聚合函数都是直接在DataFrameGroupBy上调用的,这样分组以后所有列做的都是同一种汇总运算,且一次只能使用一种汇总方式。

aggregate的神奇之处在于,一次可以使用多种汇总方式,比如下面的例子先对分组后的所有列做计数汇总运算,然后对所有列做求和汇总运算。

aggregate方法是一个既能作用于Series、DataFrame,也能作用于GroupBy的聚合方法。aggregate方法接收函数并应用于每个分组,返回标量值,其基本语法格式如下

Groupby.aggregate(func,axis=0,*args,**kwargs)
  • func : 指定用于集合运算的函数,具体类型包含自定义函数名、字符串函数名、列表函数名,字典函数名。该参数支持的统计函数是pandas、numpy、scipy、python提供的所有统计函数,也可以是自定义函数

  • axis :值为0则在列向做聚合运算,值为1则在行向做聚合运算

    • 内置函数聚合
    • df.groupby(df['city']).aggregate("count")
      
    • 自定义函数聚合
    • def Max_cut_Min(group):
          return group.max()-group.min()
      df = pd.read_csv(r'D:\study\ana_data\city_weather.csv')
      g = df.groupby(df['city']).aggregate(Max_cut_Min)
      
    • df.groupby("city").aggregate(lambda group:group.max()-group.min())  # 运用lambda表达式
      
    • 上下两个代码可以实现同样的效果

    • 单列聚合
    • g = df.groupby(df['city'])['wind'].aggregate(np.mean)
      
    • 多列聚合
    • df.groupby(df['city']).aggregate(np.mean)
      
    • 多方式聚合
    • df.groupby(df['city'])['wind'].aggregate([np.mean, np.min])
      
    • 多种聚合运算的同时更改列名
    • df.groupby(df['city'])['wind'].aggregate([('平均值', np.mean), ('最小值', np.min)])
      
    • 不同的列运用不同的聚合函数
df.groupby(df['city']).aggregate({'wind': [('平均值', np.mean), ('最小值', np.min)], 'temperature': [('平均值', np.mean), ('最大值', np.max)]})

  1. apply

  2. transform

  3. filter

透视表

透视表是一种可以对数据动态排布并且分类汇总的表格格式

(所需要用到的文件可以在网盘中自取)

字段解释:

  • Account:销售的账户
  • Name:客户名字
  • Rep:销售员
  • Manager:销售员上司
  • Product:卖出去的产品
  • Quantity:数量
  • Price:单价
  • Status:订单状态

生成透视表

pd.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All')

几个重要的参数:

  • data:DataFrame对象
  • values:源数据中的一列,数据透视表中用于观察分析的数据值,类似Excel中的值字段
  • index:源数据中的一列,数据透视表用于行索引的数据值,类似Excel中的行字段
  • columns:源数据中的一列,数据透视表用于列索引的数据值,类似Excel中列字段
  • aggfunc:根据当前的行、列索引生成的数据透视表中有多个数据需要进行聚合时,对这多个数据需要进行的操作,默认为np.mean()

通过Name字段统计

pd.pivot_table(df, index=['Name'], aggfunc='sum')

多字段透视

pd.pivot_table(df, index=['Name', 'Rep', 'Manager'])

对比

可以看出,Rep和Manager字段的先后顺序影响着透视结果,这是因为,Rep和Manager之间存在着一个多对一的关系

为了避免上述错误的产生,提前了解需要分析的数据结构很重要

单独透视某一列数据

pd.pivot_table(df, index=['Manager', 'Rep'], values=['Price'])

分组和透视功能实现

projects.fivethirtyeight.com/flights/

数据来源

(所需要用到的文件可以在网盘中自取)

字段解释:

  • flight_date:飞行日期
  • unique_carrier:航空公司号
  • flight_num:飞行号码
  • origin:机场城市
  • dest:机场名称
  • arr_delay:延迟时间
  • cancelled:是否延迟
  • distance:距离
  • ..._delay:延迟原因
  • actual_elapsed_time:飞行时间

通过延误时间排序

df.sort_values('arr_delay')[:20]

显示全部的行列数据

# 显示Dateframe所有列(参数设置为None代表显示所有行,也可以自行设置数字)
pd.set_option('display.max_columns', None)
# 显示Dateframe所有行
pd.set_option('display.max_rows', None)
# 设置Dataframe数据的显示长度,默认为50
pd.set_option('max_colwidth', 200)
# 禁止Dateframe自动换行(设置为Flase不自动换行,True反之)
pd.set_option('expand_frame_repr', False)

降序排序

df.sort_values('arr_delay', ascending=False)[:20]

计算航班延误与不延误的比例

df['cancelled'].value_counts()

判断航班是否延误

df['delayed'] = df['arr_delay'].apply(lambda x: x > 0)

求延误比例

delay_data[1]/(delay_data[0]+delay_data[1])

求各航空公司的延误比

  • 分组
delay_group = df.groupby(['unique_carrier', 'delayed'])
  • 求比
delay_group.size()

delay_group.size().unstack()

绘图和可视化之Matplotlib

官网地址:matplotlib.org/

Matplotlib介绍

实现的功能与matlab绘图功能相似

为什么要用python画图?

  • GUI太复杂(matlab绘图基于GUI)
  • excel功能不足
  • python使用简单且免费(matlab收费)

什么是Matplotlib?

  • 一个python包
  • 主要用于绘制2D图像,也可以绘制3D
  • 功能强大且流行(主要体现在深度学习中的图形化页面)
  • 拓展多(包括Seaborn)

Matplotlib的架构

  • Backend:主要处理把图显示到哪里和画到哪里
  • Artist:图像显示成什么样?
  • Scripting(脚本): pyplot ,Python语法和API

matplotlib的简单绘图---plot

简单的绘图

#单条线:
plot([x], y, [fmt], data=None, **kwargs)
#多条线一起画
plot([x], y, [fmt], [x2], y2, [fmt2], ..., **kwargs)

参数[fmt] 是一个字符串来定义图的基本属性如:颜色(color),点型(marker),线型(linestyle)

具体形式 fmt = '[color][marker][line]'

a = [1, 2, 3]
plt.plot(a)
plt.show()

多维度绘图

a = [1, 2, 3]
b = [4, 5, 6]
plt.plot(a, b, 'b*-')
plt.show()

注意: a和b的长度不一致会报错

绘制多个图像

a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9]
d = [10, 11, 12]
plt.plot(a, b, 'b*-', c, d, 'ro--')
plt.show()

绘制sin图像

t = np.arange(0.0, 2.0, 0.1)
s = np.sin(t*np.pi)
plt.plot(t, s)
plt.show()

设置label

t = np.arange(0.0, 2.0, 0.1)
s = np.sin(t*np.pi)
plt.plot(t, s)
plt.xlabel('x')
plt.ylabel('y=sinx')
plt.title('绘制sin图像')
plt.show()

直接运行显示中文标题系统会发出警报,且图像显示不出中文部分

解决办法

指定显示字体

t = np.arange(0.0, 2.0, 0.1)
s = np.sin(t*np.pi)
plt.plot(t, s)
plt.xlabel('x')
plt.ylabel('y=sinx')
plt.title('绘制sin图像', fontproperties="SimHei")
plt.show()

t = np.arange(0.0, 2.0, 0.1)
s = np.sin(t*np.pi)
plt.plot(t, s, 'b--', label='sinx')
plt.plot(t*2, s, 'r--', label='sin2x')
plt.xlabel('x')
plt.ylabel('y=sinx')
plt.title('绘制sin图像', fontproperties="SimHei")
plt.legend()
plt.show()

matplotlib的简单绘图---subplot

plt.subplot(nrows, ncols, index, **kwargs)

前三个参数分别为行数、列数、索引数

在划分子图时,并不像传统的表格一样,先划分好,然后表示要占用几个表格。而是在绘制子图的时候,告知如何划分,然后基于这种划分,在第几块进行绘制

image.png

  • 子图1:(2,2,1) ,将总图切分成两行两列,在第1个方框中绘制图像。
  • 子图2:(2,2,2),将总图切分成两行两列,在第2个方框中绘制图像
  • 子图3:(2,1,2),将总图切分成两行一列,在这样的切分下前提下,在第2个方框中绘制图像。

子图绘制

a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9]
plt.subplot(2, 1, 1)
plt.plot(a, b, 'b')
plt.ylabel("b")
plt.subplot(2, 1, 2)
plt.plot(a, c, 'r')
plt.ylabel('c')
plt.xlabel('a')
plt.show()

a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9]
d = [10, 11, 12]
plt.subplot(2, 2, 1)
plt.plot(a, b, 'b')
plt.ylabel("b")
plt.subplot(2, 2, 2)
plt.plot(a, c, 'r')
plt.ylabel('c')
plt.xlabel('a')
plt.subplot(2, 2, 3)
plt.plot(a, d, 'y--')
plt.show()

a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9]
d = [10, 11, 12]
plt.subplot(221)
plt.plot(a, b, 'b')
plt.ylabel("b")
plt.subplot(222)
plt.plot(a, c, 'r')
plt.ylabel('c')
plt.xlabel('a')
plt.subplot(223)
plt.plot(a, d, 'y--')
plt.subplot(224)
plt.plot(a, d, 'b*-')
plt.show()

subplots函数

subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True,subplot_kw=None, gridspec_kw=None, **fig_kw)

参数说明

  • nrows,ncols:子图的行列数。

  • sharex, sharey:

    • 设置为 True 或者 ‘all’ 时,所有子图共享 x 轴或者 y 轴,
    • 设置为 False or ‘none’ 时,所有子图的 x,y 轴均为独立,
    • 设置为 ‘row’ 时,每一行的子图会共享 x 或者 y 轴,
    • 设置为 ‘col’ 时,每一列的子图会共享 x 或者 y 轴。
  • squeeze:

    • 默认为 True,是设置返回的子图对象的数组格式。
    • 当为 False 时,不论返回的子图是只有一个还是只有一行,都会用二维数组格式返回他的对象。
    • 当为 True 时,如果设置的子图是(nrows=ncols=1),即子图只有一个,则返回的子图对象是一个标量的形式,如果子图有(N×1)或者(1×N)个,则返回的子图对象是一个一维数组的格式,如果是(N×M)则是返回二位格式。
  • subplot_kw:字典格式,传递给 add_subplot() ,用于创建子图。

  • gridspec_kw:字典格式,传递给 GridSpec 的构造函数,用于创建子图所摆放的网格

返回值

fig: matplotlib.figure.Figure 对象

ax:子图对象( matplotlib.axes.Axes)或者是他的数组

  1. 生成画布
figure, ax = plt.subplots( )
  1. 简单绘图
 a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9]
d = [10, 11, 12]
figure, ax = plt.subplots(2, 2)
ax[0][0].plot(a, b, 'r--')
ax[0][1].plot(c, d, 'b--')
plt.show()