Pandas 基础使用教程(1)

Pandas 是非常著名的开源数据处理库,我们可以通过它完成对数据集进行快速读取、转换、过滤、分析等一系列操作。除此之外,Pandas 拥有强大的缺失数据处理与数据透视功能,可谓是数据预处理中的必备利器。这是 Pandas 使用教程的第 1 章节,将学会安装它,并了解 Pandas 的数据结构。

官方文档

 下载官方文档(英文)

二、Pandas 安装

Linux:

sudo pip install pandas

Windows10:

pip install --upgrade pandas

 

三、 一维数据 Series

Series 是 Pandas 中最基本的 1 维数据形式。其可以储存整数、浮点数、字符串等形式的数据。Series 的新建方法如下:

s = pandas.Series(data, index=index)

其中,data 可以是字典、numpy 里的 ndarray 对象等。index 是数据索引,索引是 pandas 数据结构中的一大特性,它主要的功能是帮助我们更快速地定位数据,这一点后面会谈到。

3.1 字典 -> Series

将把不同类型的数据转换为为 Series。首先是字典类型。

import pandas as pd

d = {'a' : 10, 'b' : 20, 'c' : 30}

pd.Series(d)

 

这里,数据值是 10, 20, 30,索引为 a, b, c 。我们可以直接通过 index= 参数来设置新的索引。

d = {'a' : 10, 'b' : 20, 'c' : 30}

s = pd.Series(d, index=['b', 'c', 'd', 'a'])

s

 

你会发现,pandas 会自动匹配人为设定的索引值和字典转换过来的索引值。而当索引无对应值时,会显示为 NaN 缺失值。

3.2 ndarray -> Series

ndarray 是著名数值计算包 numpy 中的多维数组。我们也可以将 ndarray 直接转换为 Series。

import numpy as np

data = np.random.randn(5) # 一维随机数
index = ['a', 'b', 'c', 'd', 'e'] # 指定索引

s = pd.Series(data, index)

s

 

上面的两个例子中,我们都指定了 index 的值。而当我们非人为指定索引值时,Pandas 会默认从 0 开始设置索引值。

 

s = pd.Series(data)

s

 

当我们需要从一维数据 Series 中返回某一个值时,可以直接通过索引完成。

 

data = np.random.randn(5) # 一维随机数
index = ['a', 'b', 'c', 'd', 'e'] # 指定索引

s = pd.Series(data, index)
print(s)
print("------输出分割线------")
print(s['a'])

 

除此之外,Series 是可以直接进行运算的。例如:

 

data = np.random.randn(5) # 一维随机数
index = ['a', 'b', 'c', 'd', 'e'] # 指定索引

s = pd.Series(data, index)

print(s)
print("------输出分割线------")
print(2*s)
print("------输出分割线------")
print(s-s)

 

四、二维数据 DataFrame

DataFrame 是 Pandas 中最为常见、最重要且使用频率最高的数据结构。你可以想到它箱型为电子表格或 SQL 表具有的结构。DataFrame 可以被看成是以 Series 组成的字典。它和 Series 的区别在于,不但具有行索引,且具有列索引。

DataFrame 可以用于储存多种类型的输入:

  • 一维数组、列表、字典或者 Series 字典。
  • 二维 numpy.ndarray。
  • 结构化的 ndarray。
  • 一个 Series。
  • 另一个 DataFrame。

4.1 Series 字典 -> DataFrame

 

# 带 Series 的字典
d = {'one' : pd.Series([1., 2., 3.], index=['a', 'b', 'c']),'two' : pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}

df = pd.DataFrame(d)  # 新建 DataFrame

df

我们可以看到,这里的行索引为 a, b, c, d ,而列索引为 one, two

4.2 ndarrays 或 lists 字典 -> DataFrame

 

# 列表构成的字典
d = {'one' : [1, 2, 3, 4], 'two' : [4, 3, 2, 1]}

df1 = pd.DataFrame(d) # 未指定索引
df2 = pd.DataFrame(d, index=['a', 'b', 'c', 'd']) # 指定索引

print(df1)
print("------输出分割线------")
print(df2)

 

4.3 带字典的列表 -> DataFrame

 

# 带字典的列表
d = [{'a': 1, 'b': 2}, {'a': 5, 'b': 10, 'c': 20}]

df = pd.DataFrame(d)

df

 

4.4 DataFrame.from_ 方法

pandas 的 DataFrame 下面还有 4 个以 from_ 开头的方法,这也可以用来创建 Dataframe,它们分别是:

  • from_csv()
  • from_dict()
  • from_items()
  • from_records()

 

d = [('A', [1, 2, 3]), ('B', [4, 5, 6])]
c = ['one', 'two', 'three']

df = pd.DataFrame.from_items(d, orient='index', columns=c)

df

 

4.5 列选择,添加,删除

接下来,我们延续上面的 4.4 里面的数据来演示。

在一维数据结构 Series 中,我们用 df['标签'] 来选择行。而到了二维数据 DataFrame 中,df['标签'] 表示选择列了。例如:

 

df['one']

 

删除列的方法为 df.pop('列索引名'),例如:

 

df.pop('one')

df

 

添加列的方法未 df.insert(添加列位置索引序号, '添加列名', 数值),例如:

 

df.insert(2, 'four', [10, 20])

df

 

五、三维数据 Panel

Panel 是 Pandas 中使用频率较低的一种数据结构,但它是三维数据的重要容器。

5.1 面板数据

Panel data 又称面板数据,它是计量经济学中派生出来的一个概念。在计量经济学中,数据大致可分为三类:截面数据,时间序列数据,以及面板数据。而面板数据即是截面数据与时间序列数据综合起来的一种数据类型。

简单来讲,截面数据指在某一时间点收集的不同对象的数据。而时间序列数据是指同一对象在不同时间点所对应的数据集合。

这里引用一个城市和 GDP 关系的示例来解释上面的三个概念(面板数据):

截面数据:

  • 例如城市:北京、上海、重庆、天津在某一年的 GDP 分别为 10、11、9、8(单位亿元)。

时间序列数据:

  • 例如:2000、2001、2002、2003、2004 各年的北京市 GDP 分别为 8、9、10、11、12(单位亿元)。

面板数据:

  • 2000、2001、2002、2003、2004 各年中国所有直辖市的 GDP 分别为(单位亿元): 北京市分别为 8、9、10、11、12; 上海市分别为 9、10、11、12、13; 天津市分别为 5、6、7、8、9; 重庆市分别为 7、8、9、10、11。

5.2 Panel 构成

在 Pandas 中,Panel 主要由三个要素构成:

  • items: 每个项目(item)对应于内部包含的 DataFrame。
  • major_axis: 每个 DataFrame 的索引(行)。
  • minor_axis: 每个 DataFrame 的索引列。

简而言之,在 Pandas 中,一个 Panel 由多个 DataFrame 组成。下面就生成一个 Panel。

wp = pd.Panel(np.random.randn(2, 5, 4), items=['Item1', 'Item2'], major_axis=pd.date_range('1/1/2000', periods=5), minor_axis=['A', 'B', 'C', 'D'])

wp

 

我们可以看到,wp 由 2 个项目、5 个主要轴和 4 个次要轴组成。其中,主要轴由 2000-01-01 到 2000-01-05 这 5 天组成的时间序列,次轴从 A 到 D。

你可以输出 Item1 看一看。

wp['Item1']

 

再看一看 Item2。

wp['Item2']

 

可以看到,这两个 Dataframe 的行索引及列索引是一致的。由于数据是随机生成的,所以不一致。

5.2 Panel 

由于 Panel 在 Pandas 中的使用频率远低于 Series 和 DataFrame,所以 Pandas 决定在未来的版本中将 Panel 移除,转而使用 MultiIndex DataFrame 来表示多维数据结构。

这里,可以用到 Panel.to_frame() 输出多维数据结构。就拿上面的例子继续:

 

wp.to_frame()

 

你知道 Pandas 的名字是怎么来的吗?

答案:点击

https://www.shiyanlou.com/courses/906/labs/3375/document

 

 

Pandas 基础使用教程(2)- Pandas 常用的基本方法

 

1.4 官方文档

学习本课程之前,你可以先自行 下载官方文档(英文)作为辅助学习资料。

1.5 数据文件

学习本课程之前,请先打开在线环境终端,下载本文可能会用到的两个数据文件。

 

!wget http://labfile.oss.aliyuncs.com/courses/906/los_census.csv
!wget http://labfile.oss.aliyuncs.com/courses/906/los_census.txt

 

两个文件均为为洛杉矶人口普查数据,仅格式有区别。

下面的内容均在 iPython 交互式终端中演示,你可以通过在线环境左下角的应用程序菜单 > 附件打开。如果你在本地进行练习,推荐使用 Jupyter Notebook 环境。

二、Pandas 常见的基本方法

2.1 数据读取与存储

Pandas 支持大部分常见数据文件读取与存储。一般清楚下,读取文件的方法以 pd.read_ 开头,而写入文件的方法以 pd.to_ 开头。详细的表格如下。

拿刚刚下载好的数据文件举例,如果没有下载,请看 1.5 小节。

 

import pandas as pd

pd.read_csv("los_census.csv") #读取 csv 文件

 

pd.read_table("los_census.txt") #读取 txt 文件

 

其实 los_census.txt 也就是 los_census.csv 文件,因为 csv 文件又叫逗号分隔符文件,数据之间采用逗号分割。

那么,我们怎样将这种文件转换为 DataFrame 结构的数据呢?这里就要使用到读取方法中提供的一些参数了,例如 sep[] 分隔符参数。

 

pd.read_table("los_census.txt", sep=',') #读取 txt 文件

 

除了 sep,读取文件时常用的参数还有:

  1. header=,用来选择将第几行作为列索引名称。
  2. names=[],自定义列索引名称。

例如:

 

pd.read_csv("los_census.csv", header=1 ) #将第二行作为列索引名称。

 

pd.read_csv("los_census.csv", names=['A', 'B', 'C', 'D', 'E', 'F', 'G']) #自定义列索引名称。

 

好了,说了这么久的读取文件,再说一说存储文件。存储文件的方法也很简单。比如我们将 los_census.csv 文件,存储为 json 格式的文件。

 

df_los_census = pd.read_csv("los_census.csv")  #读取 csv 文件

df_los_census.to_json("1.json") # 将其存储为 json 格式文件

!ls | grep json # 列出当前目录下的 json 文件

 

当然,你也可以通过 to_excel("1.xlsx") 储存为 Excel 默认支持的 .xlsx 格式。只是,需要注意在线环境会报错。这时候需要再补充安装 openpyxl 包就好了:

 

!sudo pip install openpyxl

 

2.2 Head & Tail

有些时候,我们读取的文件很大。如果全部输出预览这些文件,既不美观,又很耗时。还好,Pandas 提供了 head() 和 tail() 方法,它可以帮助我们只预览一小块数据。

顾名思义,head() 方法就是从数据集开头预览,不带参数默认显示头部的 5 条数据,你也可以自定义显示条数。

 

df_los_census.head() # 默认显示前 5 条

 

df_los_census.head(7) # 显示前 7 条

 

tail() 方法就是从数据集尾部开始显示了,同样默认 5 条,可自定义。

 

df_los_census.tail() # 默认显示后 5 条

 

df_los_census.tail(7) # 显示后 7 条

 

2.3 统计方法

Pandas 提供了几个统计和描述性方法,方便你从宏观的角度去了解数据集。

1. describe()

describe() 相当于对数据集进行概览,会输出该数据集的计数、最大值、最小值等。

 

df_los_census.describe()

 

例如上面,针对一个 DataFrame 会对每一列的数据单独统计。

2. idxmin() & idxmax()

idxmin() 和 idxmax() 会计算最小、最大值对应的索引标签。

 

df_los_census.idxmin()

 

df_los_census.idxmax()

 

3. count()

count() 用于统计非空数据的数量。

 

df_los_census.count()

 

4.value_counts()

value_counts() 仅仅针对 Series,它会计算每一个值对应的数量统计。

 

import numpy as np

s = pd.Series(np.random.randint(0, 9, size=100)) # 生成一个 Series,并在 0-9 之间生成 100 个随机值。

print(s)
print('------分割线------')
print(s.value_counts())

 

2.4 计算方法

除了统计类的方法,Pandas 还提供了很多计算类的方法。

1. sum()

sum() 用于计算数值数据的总和。

 

df_los_census.sum()

 

2. mean()

mean() 用于计算数值数据的平均值。

 

df_los_census.mean()

 

3. median()

median() 用于计算数值数据的算术中值。

 

df_los_census.median()

 

除此之外,剩下的一些常见计算方法如下表所示。

2.5 标签对齐

索引标签是 Pandas 中非常重要的特性,有些时候,由于数据的缺失等各种因素导致标签错位的现象,或者想匹配新的标签。于是 Pandas 提供了索引标签对齐的方法 reindex()

reindex() 主要有三个作用:

  1. 重新排序现有数据以匹配新的一组标签。
  2. 在没有标签对应数据的位置插入缺失值(NaN)标记。
  3. 特殊情形下,使用逻辑填充缺少标签的数据(与时间序列数据高度相关)。

 

s = pd.Series(data=[1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])

print(s)
print('------分割线------')
print(s.reindex(['e', 'b', 'f', 'd']))

 

我们可以看到,重新排列的数据中,原有索引对应的数据能自动匹配,而新索引缺失的数据通过 NaN 补全。

当然,对于 DataFrame 类型的数据也是一样的。

 

df = pd.DataFrame(data={'one': [1, 2, 3], 'two': [4, 5, 6], 'three': [7, 8, 9]}, index=['a', 'b', 'c'])

df

 

df.reindex(index=['b', 'c', 'a'], columns=['three', 'two', 'one'])

 

你甚至还可以将上面 Series 的数据按照下面的 DataFrame 的索引序列对齐。

 

s.reindex(df.index)

 

2.6 排序

既然是数据处理,就少不了排序这一常用的操作。在 Pandas 中,排序拥有很多「姿势」,下面就一起来看一看。

1. 按索引排序

首先是按照索引排序,其方法为Series.sort_index()或者是DataFrame.sort_index()

 

df = pd.DataFrame(data={'one': [1, 2, 3], 'two': [4, 5, 6], 'three': [7, 8, 9], 'four': [10, 11, 12]}, index=['a', 'c', 'b'])

df

 

下面按索引对行重新排序:

 

df.sort_index()

 

或者添加参数,进行倒序排列:

 

df.sort_index(ascending=False)

 

1. 按数值排序

第二种是按照数值排序,其方法为Series.sort_values()或者是DataFrame.sort_values()。举个例子:

 

df = pd.DataFrame(data={'one': [1, 2, 3, 7], 'two': [4, 5, 6, 9], 'three': [7, 8, 9, 2], 'four': [10, 11, 12, 5]}, index=['a', 'c', 'b','d'])

df

 

将第三列按照从小到大排序:

 

df.sort_values(by='three')

也可以同时按照两列:

 

df[['one', 'two', 'three', 'four']].sort_values(by=['one','two'])

 

Pandas 基础使用教程(3)- Pandas 数据选择与过滤

 

1.5 数据文件

学习本课程之前,请先打开在线环境终端,下载本文可能会用到的两个数据文件。

!wget http://labfile.oss.aliyuncs.com/courses/906/los_census.csv

los_census.csv 为为洛杉矶人口普查数据,仅格式有区别。

下面的内容均在 iPython 交互式终端中演示,你可以通过在线环境左下角的应用程序菜单 > 附件打开。如果你在本地进行练习,推荐使用 Jupyter Notebook 环境。

二、数据选择

在数据预处理过程中,我们往往会对数据集进行切分,只将需要的某些行、列,或者数据块保留下来,输出到下一个流程中去。这也就是这里所说的数据选择。

由于 Pandas 的数据结构中存在索引、标签,所以我们可以通过多轴索引完成对数据的选择。

2.1 基于索引数字选择

当我们新建一个 DataFrame 之后,如果未自己指定行索引或者列对应的标签,那么 Pandas 会默认从 0 开始以数字的形式作为行索引,并以数据集的第一行作为列对应的标签。其实,这里的「列」也有数字索引,默认也是从 0 开始,只是未显示出来。

所以,我们首先可以基于数字索引对数据集进行选择。这里用到的 Pandas 中的 .iloc 方法。该方法可以接受的类型有:

  1. 整数。例如:5
  2. 整数构成的列表或数组。例如:[1, 2, 3]
  3. 布尔数组。
  4. 可返回索引值的函数或参数。

下面,我们还是用 los_census.csv 数据集演示该方法的使用。如果未下载该数据集,请看 1.5 节。

 

import pandas as pd

df = pd.read_csv("los_census.csv")

df.head()

首先,我们可以选择前 3 行数据。这和 python 或者 numpy 里面的切片很相似。

 

df.iloc[:3]

我们还可以选择特定的一行。

 

df.iloc[5]

那么选择多行是不是 print df.iloc[1, 3, 5] 这样呢?答案是错误的。df.iloc[] 的 [[行],[列]] 里面可以同时接受行和列的位置,如果你直接键入 df.iloc[1, 3, 5] 就会报错。

所以,很简单。如果你想要选择 1,3,5 行,可以这样做。

 

df.iloc[[1, 3, 5]]

选择行学会以后,选择列就应该能想到怎么办了。你可以先暂停浏览下面的内容,自己试一试。

例如,我们要选择第 2-4 列。

df.iloc[:, 1:4]

这里选择 2-4 列,输入的却是 1:4。这和 python 或者 numpy 里面的切片操作非常相似。

既然我们能定位行和列,那么只需要组合起来,我们就可以选择数据集中的任何一块数据了。

2.2 基于标签名称选择

除了根据数字索引选择,我们还可以直接根据标签对应的名称选择。这里用到的方法和上面的 iloc 很相似,少了个 i 为 df.loc[]

df.loc[] 可以接受的类型有:

  1. 单个标签。例如:2 或 'a',这里的 2 指的是标签而不是索引位置。
  2. 列表或数组包含的标签。例如:['A', 'B', 'C']
  3. 切片对象。例如:'A':'E',注意这里和上面切片的不同支持,首位都包含在内。
  4. 布尔数组。
  5. 可返回标签的函数或参数。

下面,我们来演示 df.loc[] 的用法。我们先随机生成一个 DataFrame。

 

import numpy as np  # 加载 numpy 模块

df = pd.DataFrame(np.random.randn(6,5),index=list('abcdef'),columns=list('ABCDE'))

df

先选择前 3 行:

 

df.loc['a':'c']

再选择 1,3,5 行:

 

df.loc[['a', 'c', 'd']]

然后,选择 2-4 列:

 

df.loc[:, 'B':'D']

最后,选择 1,3 行和 C 后面的列:

df.loc[['a','c'], 'C':]

2.3 数据随机取样

上面,的 .iloc 和 .loc 可用于精准定位数据块。而 Pandas 同样也提供了随机取样的方法,用于满足各种情况。随机取样用 .sample() 完成,下面我们就演示一下它的用法。

首先,看一看 Series 数据结构。

 

s = pd.Series([0,1,2,3,4,5,6,7,8,9])

s.sample()

我们可以看到,默认情况下 .sample() 返回了一个数值。注意,前面的 2 是数字索引,后面的 2 才是值。

我们可以通过 n= 参数,设定返回值的数量。

 

s.sample(n=5)

同样也可以用 frac= 参数设定返回数量的比例。

 

s.sample(frac=.6) # 返回 60% 的数值

对应 DataFrame 而言,过程也很相似,只是需要选择坐标轴。举个例子:

☞ 示例代码:

df = pd.DataFrame(np.random.randn(6,5),index=list('abcdef'),columns=list('ABCDE'))

df.sample(n=3)

默认会返回行,如果要随机返回 3 列。需要添加 axis= 参数。

 

df.sample(n=3, axis=1)

2.4 条件语句选择

数据选择的时候,我们还可以加入一些条件语句,从而达到对数据筛选的目的。这个过程和 numpy 里面的效果很相似。我们先举一个 Series 的例子:

 

s = pd.Series(range(-5, 5))
s

 

s[(s < -2) | (s > 1)] # 添加 逻辑或 条件

对于 DataFrame 也是相似的。

 

df = pd.DataFrame(np.random.randn(6,5),index=list('abcdef'),columns=list('ABCDE'))

df

 

df[(df['B'] > 0) | (df['D'] < 0)]  # 添加条件

2.5 where() 方法选择

接下来,再介绍一种通过 where() 方法进行数据选择得方法。DataFrame 和 Series 都带有 where(),可以通过一些判断句来选择数据。举个例子:

 

df = pd.DataFrame(np.random.randn(6,5),index=list('abcdef'),columns=list('ABCDE'))

df

 

df.where(df < 0)  # 添加条件

.where(df < 0) 会返回所有负值,而非负值就会被置为空值 NaN。 你也可以对判断条件以外得值重新替代,例如这里将非负值全部变号为负值。

 

df.where(df < 0, -df)  # 筛选负值并将正值变号

故,where() 实际上期待了匹配和替换得效果。我们可以借助该方法实现对数据的自由设定。

2.6 query() 方法选择

针对数据变换和筛选的方法还很多,除了上面的提到的,Pandas 0.13 之后的版本中增加了 query() 实验性方法,该方法也可以被用来选择数据。

query() 是 DataFrame 具有的方法,你可以通过一个比较语句对满足行列条件的值进行选择,举个例子:

 

df = pd.DataFrame(np.random.rand(10, 5), columns=list('abcde'))

df

 

df.query('(a < b) & (b < c)')  # 添加 逻辑与 条件

上面的判断语句应该很容易看明白,也就是满足 a 列的值需小于 b 列,且 b 列的值小于 c 列所在的行。

当然,在没有 query() 之前,我们也是可以通过前面提到的条件语句选择。

 

df[(df.a < df.b) & (df.b < df.c)]

结果虽然一致,但是 query() 语句的确要简洁和自然很多。query() 包含很多内容,非常强大。你可以通过官方文档了解,这里就不再赘述了。

针对数据集的处理,无外乎就是变换、筛选,最终得到我们想要的数据。

 

 

Pandas 基础使用教程(4)- Pandas 进行缺失值处理

 

二、认识缺失值

在真实的生产环境中,我们需要处理的数据文件往往没有想象中的那么美好。其中,很大几率会遇到的情况就是缺失值。

2.1 什么是缺失值?

缺失值主要是指数据丢失的现象,也就是数据集中的某一块数据不存在。除此之外、存在但明显不正确的数据也被归为缺失值一类。例如,在一个时间序列数据集中,某一段数据突然发生了时间流错乱,那么这一小块数据就是毫无意义的,可以被归为缺失值。

当然,除了原始数据集就已经存在缺失值以外。当我们用到前面章节中的提到的索引对齐(reindex())的方法时,也容易人为导致缺失值的产生。举个例子:

首先,我们生成一个 DataFrame。

 

import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.rand(5, 5), index=list('cafed'),columns=list('ABCDE'))

df

 

然后,我们使用 reindex()` 完成索引对齐。

 

df.reindex(list('abcde'))

 

由于原始数据集中,没有索引 b,所以对齐之后,b 后面全部为缺失值,也就造成了数据缺失。

2.2 检测缺失值

Pandas 为了更方便地检测缺失值,将不同类型数据的缺失均采用 NaN 标记。这里的 NaN 代表 Not a Number,它仅仅是作为一个标记。例外是,在时间序列里,时间戳的丢失采用 NaT 标记。

Pandas 中用于检测缺失值主要用到两个方法,分别是:isnull() 和 notnull(),故名思意就是「是缺失值」和「不是缺失值」。默认会返回布尔值用于判断。

下面,演示一下这两个方法的作用,我们这里沿用上面进行索引对齐后的数据。

 

df2 = df.reindex(list('abcde'))

df2.isnull()

 

df2.notnull()

 

然后,我们来看一下对时间序列缺失值的检测,对上面的 df2 数据集进行稍微修改。

 

# 插入 T 列,并打上时间戳
df2.insert(value=pd.Timestamp('2017-10-1'),loc=0,column='T')

# 将 T 列的 1,3,5 行置为缺失值
df2.loc[['a','c','e'],['T']] = np.nan

 

这里,我们也更清晰看到,时间序列的缺失值用 NaT 标记。我们对 df2 进行缺失值检测。

 

df2.isnull()

 

df2.notnull()

 

三、填充和清除缺失值

上面已经对缺省值的产生、检测进行了介绍。那么,我们面对缺失值时,到底有哪些实质性的措施呢?接下来,就来看一看如何完成对缺失值填充和清除。

填充和清除都是两个极端。如果你感觉有必要保留缺失值所在的列或行,那么就需要对缺失值进行填充。如果没有必要保留,就可以选择清除缺失值。

Pandas 中,填充缺失的方法为 fillna(),清除为 dropna()

3.1 填充缺失值 fillna()

首先,我们看一看 fillna() 的使用方法。重新打开一个 ipython 终端,我们生成和上面相似的数据。

 

df = pd.DataFrame(np.random.rand(9, 5), columns=list('ABCDE'))

# 插入 T 列,并打上时间戳
df.insert(value=pd.Timestamp('2017-10-1'),loc=0,column='Time')

# 将 1, 3, 5 列的 1,3,5 行置为缺失值
df.iloc[[1,3,5,7], [0,2,4]] = np.nan

# 将 2, 4, 6 列的 2,4,6 行置为缺失值
df.iloc[[2,4,6,8], [1,3,5]] = np.nan

 

我们用相同的标量值替换 NaN,比如用 0

 

df.fillna(0)

 

注意,这里的填充并不会直接覆盖原数据集,你可以重新输出 df 比较结果。

除了直接填充值,我们还可以通过参数,将缺失值前面或者后面的值填充给相应的缺失值。例如使用缺失值前面的值进行填充:

 

df.fillna(method='pad')

 

或者是后面的值:

 

df.fillna(method='bfill')

 

最后一行由于没有对于的后序值,自然继续存在缺失值。

上面的例子中,我们的缺失值是间隔存在的。那么,如果存在连续的缺失值是怎样的情况呢?试一试。首先,我们将数据集的第 2,4 ,6 列的第 3,5 行也置为缺失值。

 

df.iloc[[3,5], [1,3,5]] = np.nan

 

然后来正向填充:

 

df.fillna(method='pad')

 

可以看到,连续缺失值也是按照前序数值进行填充的,并且完全填充。这里,我们可以通过 limit= 参数设置连续填充的限制数量。

 

df.fillna(method='pad', limit=1)

除了上面的填充方式,还可以通过 Pandas 自带的求平均值方法等来填充特定列或行。举个例子:

 

df.fillna(df.mean()['C':'E'])

 

对 C 列和 E 列用平均值填充。

3.2 清除缺失值 dropna()

上面演示了缺失值填充。但有些时候,缺失值比较少或者是填充无意义时,就可以直接清除了。

由于填充和清除在赋值之前,均不会影响原有的数据。所以,我们这里依旧延续使用上面的 df

 

df.dropna()

 

我们可以看到,dropna() 方法带来的默认效果就是,凡是存在缺失值的行均被直接移除。此时,dropna() 里面有一个默认参数是 axis=0,代表依据行来移除。

如果我们像将凡是有缺失值的列直接移除,可以将 axis=1,试一试。☞ 示例代码:

df.dropna(axis=1)

 

由于上面的 df 中,每一列都有缺失值,所以全部被移除了。

四、插值 interpolate()

插值是数值分析中一种方法。简而言之,就是借助于一个函数(线性或非线性),再根据已知数据去求解未知数据的值。插值在数据领域非常常见,它的好处在于,可以尽量去还原数据本身的样子。

Pandas 中的插值,通过 interpolate() 方法完成,默认为线性插值,即 method='linear'。除此之外,还有{‘linear’, ‘time’, ‘index’, ‘values’, ‘nearest’, ‘zero’, ‘slinear’, ‘quadratic’, ‘cubic’, ‘barycentric’, ‘krogh’, ‘polynomial’, ‘spline’, ‘piecewise_polynomial’, ‘from_derivatives’, ‘pchip’, ‘akima’}等插值方法可供选择。

举个例子:

# 生成一个 DataFrame
df = pd.DataFrame({'A': [1.1, 2.2, np.nan, 4.5, 5.7, 6.9], 'B': [.21, np.nan, np.nan, 3.1, 11.7, 13.2]})
df

 

对于上面存在的缺失值,如果通过前后值,或者平均值来填充是不太能反映出趋势的。这时候,插值最好使。我们用默认的线性插值试一试。

 

df_interpolate = df.interpolate()
df_interpolate

 

如果你熟悉 Matplotlib,我们可以将数据绘制成图看一看趋势。

 

%matplotlib inline
from matplotlib import pyplot as plt
plt.style.use('ggplot')
df_interpolate.plot(
    x='A',
    y='B'
)

 

图中,第 2,3 点的坐标是我们插值的结果。

上面提到了许多插值的方法,也就是 method=。下面给出几条选择的建议:

  1. 如果你的数据增长速率越来越快,可以选择 method='quadratic'二次插值。
  2. 如果数据集呈现出累计分布的样子,推荐选择 method='pchip'
  3. 如果需要填补缺省值,以平滑绘图为目标,推荐选择 method='akima'

当然,最后提到的 method='akima',需要你的环境中安装了 Scipy 库。除此之外,method='barycentric' 和 method='pchip' 同样也需要 Scipy 才能使用。

 

本章节学习了如何通过 Pandas 处理数据集中的缺失值。并了解了检测缺失值、填充缺失值、清除缺失值以及相关的插值方法。当然,这些内容相对于强大的 Pandas 而言仅仅是开始,每一种方法都还包含很多参数。入门之后,需要通过官方文档来学习更高阶的使用方法。

六、课后作业

自己尝试生成一个 DataFrame,并制造一些零散的缺失值。最后,通过不同的插值方法完成缺失值的填充。

 

 

Pandas 基础使用教程(5)- Pandas 时间序列分析

 

1.2 实验知识点

  • 时间戳 Timestamp
  • 时间索引 DatetimeIndex
  • 时间转换 to_datetime
  • 时间序列检索
  • 时间序列计算

二、时间序列分析介绍

2.1 简介

时间序列(英语:time series)是实证经济学的一种统计方法,它是采用时间排序的一组随机变量,国内生产毛额(GDP)、消费者物价指数(CPI)、股价指数、利率、汇率等等都是时间序列。时间序列的时间间隔可以是分秒(如高频金融数据),可以是日、周、月、季度、年、甚至更大的时间单位。[维基百科]

我们针对时间序列数据进行挖掘的过程又被成为时间序列分析,简称:时序分析。

2.2 常见问题

Pandas 经常被用于处理与时间序列相关的数据,尤其是像财务数据。在处理时间序列数据时,会遇到各类需求,包括但不限于:

  1. 生成固定跨度的时期构成时间序列。
  2. 将现有的时间序列,转换成需要的时间序列格式。
  3. 计算序列中的相对时间,例如:每季度的第一周。

三、Pandas 处理时间序列

接下来,我们就时间序列中常遇到的一些需求类型,列举一些示例,并使用 Pandas 提供的方法进行处理。

3.1 时间戳 Timestamp

既然是时间序列类型的数据,那么就少不了时间戳这一关键元素。Pandas 中,我们有两个创建时间戳的方法,分别是:to_datatime和 Timestamp

to_datatime 后面集中详说。首先看一看 Timestamp,它针对于单一标量,举个例子:

 

import pandas as pd

pd.Timestamp('2017-10-01')

 

如果要包含小时:分钟:秒:

 

pd.Timestamp('2017-10-01 13:30:59')

 

当然,还支持其他的格式输入,比如:

 

pd.Timestamp('1/10/2017 13:30:59')

 

3.2 时间索引 DatetimeIndex

在实际工作中,我们很少遇到用单个时间戳的情况。而大多数时候,是使用由时间戳构成的时间索引。

首先,我们来看一下如何使用 Pandas 创建时间索引。这里用到的方法为 date_range()date_range() 和 python 自带的 range() 很相似。它可以用来创建一系列等间距时间,并作为 Series 或者 DataFrame 的索引。

date_range() 方法带有的默认参数如下:

pandas.date_range(start=None, end=None, periods=None, freq=’D’, tz=None, normalize=False,
name=None, closed=None, **kwargs)

常用参数的含义如下:

  • start= :设置起始时间
  • end=:设置截至时间
  • periods= :设置时间区间,若 None 则需要设置单独设置起止和截至时间。
  • freq= :设置间隔周期,默认为 D,也就是天。可以设置为小时、分钟、秒等。
  • tz=:设置时区。

举个例子:

pd.date_range('1/10/2017', periods=24, freq='H')

 

可以这样:

 

pd.date_range('1/10/2017', periods=10, freq='D')

 

我们可以发现 freq= 参数的特点:

  • freq='s': 秒
  • freq='min' : 分钟
  • freq='H': 小时
  • freq='D': 天
  • freq='w': 周
  • freq='m': 月

除了上面这些参数值,还有一些特别的:

  • freq='BM': 每个月最后一天
  • freq='W':每周的星期日

如果你想同时按天、小时更新,也是可以的。但需要像下面这样设置参数值:

 

pd.date_range('1/10/2017', periods=20, freq='1H20min')

 

所以,只要适当地组合,你可以生成任意想要的时间序列索引。

3.3 时间转换 to_datatime

to_datatime 是 Pandas 用于处理时间序列时的一个重要方法,它可以将实参转换为时间戳。to_datatime 包含的默认参数如下:

pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, box=True, format=None, exact=True, unit=None, infer_datetime_format=False, origin='unix')
  • arg:可以接受整数、浮点数、字符串、时间、列表、元组、一维数组、Series 等。
  • errors=:默认为 raise,表示遇到无法解析数据将会报错。还可以设置为 coerce,表示无法解析设为 NaT,或者设为 ignore 忽略错误。
  • dayfirst= :表示首先解析日期,例如:1/10/17 被解析为 2017-10-1
  • yearfirst= :表示首先解析年,例如:1/10/17 被解析为 2001-10-17
  • utc=:返回 UTC 格式时间索引。
  • box=True 表示返回时间索引 DatatimeIndexFalse 表示返回多维数组 ndarray
  • format= :时间解析格式,例如:%d /%m /%Y

对于 to_datatime 的返回值而言:

  • 输入列表,默认返回时间索引 DatetimeIndex
  • 输入 Series,默认返回 datetime64 的 Series
  • 输入标量,默认返回时间戳 Timestamp

下面,针对输入数据类型的不同,我们来看一看 to_datatime 的不同用法。

3.3.1 输入标量

 

pd.to_datetime('1/10/2017 10:00', dayfirst=True)

 

3.3.2 输入列表

pd.to_datetime(['1/10/2017 10:00','2/10/2017 11:00','3/10/2017 12:00'])

 

3.3.2 输入 Series

pd.to_datetime(pd.Series(['Oct 11, 2017', '2017-10-2', '3/10/2017']), dayfirst=True)

 

3.3.2 输入 DataFrame

 

pd.to_datetime(pd.DataFrame({'year': [2017, 2018], 'month': [9, 10], 'day': [1, 2], 'hour': [11, 12]}))

3.3.2 errors=

接下来,看一看 errors= 遇到无法解析的数据时,所对应的不同返回值。这个参数对于我们解析大量数据时非常有用。

 

pd.to_datetime(['2017/10/1', 'abc'], errors='raise')

 

如果对错误不处理,这里会出现 ValueError: Unknown string format 的报错信息。

 

# 忽略错误
pd.to_datetime(['2017/10/1', 'abc'], errors='ignore')

 

# 错误设置为 NaT
pd.to_datetime(['2017/10/1', 'abc'], errors='coerce')

3.4 时间序列检索

上面,我们介绍了时间索引 DatetimeIndex 的生成方法。那么,它主要是用来做什么呢?

答案当然是 Pandas 对象的索引啦。将时间变成索引的优点非常多,包含但不限于:

  1. 查找和检索特定日期的字段非常快。
  2. 进行数据对齐时,拥有相同时间间隔的索引的数据将会非常快。
  3. 可以很方便地通过 shift 和 ishift 方法快速移动对象。

下面,针对时间序列索引的检索等操作举几个例子。首先,我们生成 10 万条数据:

import numpy as np
ts = pd.DataFrame(np.random.randn(100000,1), columns=['Value'], index=pd.date_range('20170101', periods=100000, freq='T'))

ts

☞ 动手练习:

当我们对数据进行快速检索是,其实和除了 Series 和 DataFrame 数据别无二致。例如:

检索 2017 年 3 月 2 号的数据:

ts['2017-3-2']

 

一共 1440 行。 检索 2017 年 3 月 3 号下午 2 点到 5 点 23 分之间的数据:

 

ts['2017-3-2 14:00:00':'2017-3-2 17:23:00']

 

一共返回了 204 行。 总之,一切在 Series 和 DataFrame 上可以用的数据选择与定位的方法,像 iloc()loc() 等均可以用于时间序列,这里就不再赘述了。

3.5 时间序列计算

在 Pandas 中,包含有很多可以被加入到时间序列计算中去的类,这些被称为 Offsets 对象。

关于这一点,我们举出几个例子就一目了然了。例如:

 

from pandas.tseries import offsets # 载入 offsets

dt = pd.Timestamp('2017-10-1 10:59:59')

dt + offsets.DateOffset(months=1, days=2, hour=3) # 增加时间

 

又或者我们减去 3 个周的时间:

dt - offsets.Week(3)

 

看明白了吧。这类的对象非常多,就不再一一演示,通过表格列举如下:

3.6 其他方法

最后,再介绍几个与时间序列处理相关的方法。

移动 Shifting

shifting 可以将数据或者时间索引沿着时间轴的方向前移或后移,举例如下:

 

ts = pd.DataFrame(np.random.randn(7,2), columns=['Value1','Value2' ], index=pd.date_range('20170101', periods=7, freq='T'))

ts

 

接下来开始移动。

 

ts.shift(3)

 

默认是数据向后移动。这里数据值向后移动了 3 行。

 

ts.shift(-3)

 

可以通过添加负号,使得向前移动。

那么,想移动索引怎么办?这里使用 tshift()。

ts.tshift(3)

 

向前移动索引就不再演示了,同样可以通过负号完成。除此之外,shift() 是可以接受一些参数的,比如 freq=''。而这里的 freq='' 参数和上文在介绍 时间索引 DatetimeIndex 时提到的一致。

举个例子:

 

ts.shift(3, freq='D') # 日期向后移动 3 天

 

所以说,shifting 可以让我们更加灵活地去操作时间序列数据集,完成数据对齐等目标。

重采样 Resample

重采样,即是将时间序列从一个频率转换到另一个频率的过程。实施重采样的情形如下:

  1. 有时候,我们的时间序列数据集非常大,比如百万级别甚至更高。如果将全部数据用于后序计算,其实很多情况下是没有必要的。此时,我们可以对原有的时间序列进行降频采样。
  2. 除了上面的情形,重采样还可以被用于数据对齐。比如,两个数据集,但是时间索引的频率不一致,这时候,可以通过重采样使二者频率一致,方便数据合并、计算等操作。

下面,我们看一看 resample() 的使用。首先,还是生成一个数据集。

 

# 生成一个时间系列数据集
ts = pd.DataFrame(np.random.randn(50,1), columns=['Value' ], index=pd.date_range('2017-01', periods=50, freq='D'))
ts

 

首先,可以升频采样,间隔变成小时。但是,由于间隔变小,我们就必须对新增加的行进行填充。

 

ts.resample('H').ffill()

 

下面,接着开始降频采样,从 1 天变成 5 天:

 

ts.resample('5D').sum()

 

四、实验总结

本章节介绍了利用 Pandas 对时间序列数据进行处理的一些手段,重点演示了时间索引的构建、时间索引转换以及移动、重采样等方法。当然,文中对这些方法的介绍依然还不够细致。如果你需要在实际工作中进行数据处理,还需要对照官方文档熟悉每一个方法的每一个参数的使用,这样才能发挥出 Pandas 的强大作用。