在进行数据分析和建模的过程中,通常要花大量时间在数据准备上:加载、清洗、转换和重新排列。据统计,此类任务通常占用数据分析师 80% 以上的时间。很多情况是,数据在文件或数据库中的存储方式、格式不适合特定任务。那么研究人员会选择使用通用编程语言(如 Python、Perl、R 或 Java)或 Unix 文本处理工具(如 sed 或 awk)将数据从一种形式临时处理为另一种形式。为了提高处理效率,pandas 以及内置的 Python 语言功能提供了一组高级、灵活且快速的工具,使我们能够将数据处理为正确的形式。
接下来我先学习用于缺失数据、重复数据、字符串操作和分析数据转换的工具。然后重点学习以各种方式组合和重新排列数据集。
一、处理缺失数据
缺失数据在数据分析应用程序中很常见。pandas 的目标之一是使处理缺失数据尽可能轻松。例如,默认情况下,pandas 对象上的所有描述性统计信息都排除了缺失数据进行计算。
在 pandas 对象中表示缺失数据的方式有些不完美,但对于大多数实际使用来说已经足够了。对于具有 float64 dtype 的数据,pandas 使用浮点值 NaN(非数字)来表示缺失数据。我们称之为 sentinel 值:如果存在sentinel 值,则表示缺失(或 null)值。
在 pandas 中,采用了 R 编程语言中使用的约定,将缺失数据称为 NA,即 not available。在统计应用程序中,NA 数据可能是不存在的数据,也可能是存在但未观察到的数据(例如,由于数据收集问题造成的未观察到的)。在清理数据进行分析时,对缺失数据本身进行分析,以识别是否存在数据收集问题或由缺失数据引起的数据潜在偏差通常很重要。
内置的 Python None 值也被视为 NA。pandas 项目会尽量使不同数据类型之间处理缺失数据保持一致。
import numpy as np
import pandas as pd
float_data = pd.Series([1.5, -3.2, np.nan, 0])
print(float_data)
#用isna函数探查是否存在缺失值,返回一个bool数组,其中缺失值为True
float_data.isna()
#Python None 值也被视为 NA
string_data = pd.Series(["zhuhai", np.nan, None, "nanchang"])
print(string_data)
print(string_data.isna())
#float64 数据,pandas使用浮点值 NaN(非数字)来表示缺失数据,所以None会表示为NaN
float_data = pd.Series([1, 2, 3, None], dtype='float64')
print(float_data)
print(float_data.isna())
以上的输出结果分别是(看注释):
0 1.5
1 -3.2
2 NaN
3 0.0
dtype: float64
0 zhuhai
1 NaN
2 None
3 nanchang
dtype: object
0 False
1 True
2 True
3 False
dtype: bool
0 1.0
1 2.0
2 3.0
3 NaN
dtype: float64
0 False
1 False
2 False
3 True
dtype: bool
以下列表是处理缺失数据的一些常用函数。
1.过滤缺失数据
之前我们学习过用pandas.isna()和布尔索引来过滤选择缺失数据,但还有其他更简单的方法,比如使用dropna()。在 Series 上,它返回仅包含非 null 数据和索引值的 Series。
import numpy as np
import pandas as pd
data = pd.Series([1, np.nan, 3, np.nan, 5, np.nan, 7])
print(data.dropna())
#使用data[data.notna()]和上面的代码效果一样
print(data[data.notna()])
输出:
0 1.0
2 3.0
4 5.0
6 7.0
dtype: float64
对于 DataFrame 对象,有多种方法可以删除缺失的数据。我们可能希望删除全部为 NA 的行或列,或者仅删除包含任何 NA 的行或列。默认情况下,dropna() 会删除包含缺失值的任何行。设置参数 how=“all” 将仅删除元素值全部为 NA 的行:
import numpy as np
import pandas as pd
data = pd.DataFrame([[1., 6.6, 3.], [2., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.8, 3.]])
print(data)
# 打印 默认删除了DataFrame对象data中包含了缺失值的任何行 后的数据
print(data.dropna())
# data.dropna(how="all")设置参数 how=“all” 将仅删除元素值全部为 NA 的行
print(data.dropna(how="all"))
输出结果data:
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | 6.6 | 3.0 |
1 | 2.0 | NaN | NaN |
2 | NaN | NaN | NaN |
3 | NaN | 6.8 | 3.0 |
data.dropna()输出结果:
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | 6.6 | 3.0 |
data.dropna(how="all")输出结果:
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | 6.6 | 3.0 |
1 | 2.0 | NaN | NaN |
3 | NaN | 6.8 | 3.0 |
注意:以上函数默认返回新对象,并且不会修改原始对象的内容。
我们能以相同的方式过滤列,即使用参数 axis=“columns”。我们给data对象增加一个空列使用如下代码:data[4] = np.nan 则data输出如下(增加了列标签为4,值为NaN的列):
0 | 1 | 2 | 4 | |
---|---|---|---|---|
0 | 1.0 | 6.6 | 3.0 | NaN |
1 | 2.0 | NaN | NaN | NaN |
2 | NaN | NaN | NaN | NaN |
3 | NaN | 6.8 | 3.0 | NaN |
我们使用如下代码来过滤所有元素值都为NaN的列:
import numpy as np
import pandas as pd
data = pd.DataFrame([[1., 6.6, 3.], [2., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.8, 3.]])
#在data上增加列标签为4,值为NaN的列
data[4] = np.nan
#使用axis参数从columns方向过滤列中元素都是缺失值的列
a = data.dropna(axis="columns", how="all")
print(a)
输出结果:
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | 6.6 | 3.0 |
1 | 2.0 | NaN | NaN |
2 | NaN | NaN | NaN |
3 | NaN | 6.8 | 3.0 |
如果我们想保留最多包含一定数量的缺失值的行,可以使用 thresh 参数来实现 ,比如设置thresh=2,表示过滤掉最少有2个缺失值的行。例如:
import numpy as np
import pandas as pd
#用numpy的标准正态分布函数返回的数组构造一个DataFrame对象
df = pd.DataFrame(np.random.standard_normal((7, 3)))
#将df的位置为(0,1)、(1,1)、(2,1)、(3,1)的元素设置为缺失值
df.iloc[:4, 1] = np.nan
#将df的位置为(0,2)、(1,2)的元素设置为缺失值
df.iloc[:2, 2] = np.nan
print(df)
# df.dropna()过滤掉有缺失值的行
print(df.dropna())
# df.dropna(thresh=2)过滤掉至少有2个缺失值的行
print(df.dropna(thresh=2))
df输出:
0 | 1 | 2 | |
---|---|---|---|
0 | -0.758732 | NaN | NaN |
1 | 1.074749 | NaN | NaN |
2 | 0.027023 | NaN | -0.527924 |
3 | 0.378674 | NaN | -0.673659 |
4 | -0.553535 | -1.500075 | -0.874450 |
5 | -0.178108 | -1.717260 | -0.805016 |
6 | -0.402567 | 0.093197 | -0.764875 |
df.dropna()输出:
0 | 1 | 2 | |
---|---|---|---|
4 | -0.553535 | -1.500075 | -0.874450 |
5 | -0.178108 | -1.717260 | -0.805016 |
6 | -0.402567 | 0.093197 | -0.764875 |
df.dropna(thresh=2)输出:
2.填充缺失数据
在数据处理分析建模的很多情况下,我们不希望过滤掉缺失数据,而是希望用某些值来填充,要填充值常用的一个方法是使用fillna()函数,使用 fillna()会将缺失值替换为该函数的参数值。例如上面的df,df.fillna(0)则会将NaN值都替换为0.000000。
df.fillna(0)输出:
在fillna()函数中使用字典调参数,可以为每列使用不同的填充值:df.fillna({1: 0.5, 2: 0})表示第1列用0.5填充,第二列用0填充。df.fillna({1: 0.5, 2: 0})输出如下:
下表列出了fillna()函数的参数列表:
我们可以使用fillna()函数的method参数指定填充方法,插值方法: “bfill” (向后填充) 或 “ffill” (向前填充);默认值为 None。limit参数指定:对于前向填充和向后填充,要填充的最大连续期间数。例如:
import numpy as np
import pandas as pd
df = pd.DataFrame(np.random.standard_normal((6, 3)))
df.iloc[2:, 1] = np.nan
df.iloc[4:, 2] = np.nan
print(df)
a = df.fillna(method="ffill")
print(a)
b = df.fillna(method="ffill", limit=2)
print(b)
df输出:
0 1 2
0 0.282442 2.073431 1.359409
1 -1.368045 0.327632 1.530998
2 0.067651 NaN 1.164222
3 -0.956539 NaN -0.826733
4 1.541860 NaN NaN
5 -0.589675 NaN NaN
df.fillna(method="ffill")之后输出:
0 1 2
0 0.282442 2.073431 1.359409
1 -1.368045 0.327632 1.530998
2 0.067651 0.327632 1.164222
3 -0.956539 0.327632 -0.826733
4 1.541860 0.327632 -0.826733
5 -0.589675 0.327632 -0.826733
df.fillna(method="ffill")这个语句控制台会输出一个警告,因为我使用的是最新版的pandas,提示这种方式使用fill已经被替换了:df.ffill()。
df.fillna(method="ffill", limit=2)之后输出:
0 1 2
0 0.282442 2.073431 1.359409
1 -1.368045 0.327632 1.530998
2 0.067651 0.327632 1.164222
3 -0.956539 0.327632 -0.826733
4 1.541860 NaN -0.826733
5 -0.589675 NaN -0.826733
这个语句df.fillna(method="ffill", limit=2)也会提示一个警告,新版用df.ffill(limit=2)替代。基于以上的提示我们修改下代码:
import numpy as np
import pandas as pd
df = pd.DataFrame(np.random.standard_normal((6, 3)))
df.iloc[2:, 1] = np.nan
df.iloc[4:, 2] = np.nan
print(df)
#a = df.fillna(method="ffill")
a = df.ffill()
print(a)
#b = df.fillna(method="ffill", limit=2)
b = df.ffill(limit=2)
print(b)
使用 fillna,可以执行许多其他操作,例如使用中位数或平均值统计量进行简单的数据插补:
import numpy as np
import pandas as pd
data = pd.Series([1., np.nan, 3, np.nan, 5, np.nan, 7])
# 用平均值填充
a = data.fillna(data.mean())
print(a)
以上用平均值来填充缺失值。