简介:上一篇介绍了如何用Python读取和存储数据。在数据收集的时候,原始数据总是或多或少存在一些问题(缺失、异常、格式不统一等等)。今天,我们就来说一说如何用Python进行数据的预处理(也可以叫做“清洗”)。
标签:计算机技术,Python
人生苦短,我用Python。 —— 鲁迅
引 子:
在半导体制造公司里,每天都会产生数以亿计的生产和测试数据。采用自动化的脚本语言对生产测试数据进行批量,将大大提高工程师的工作效率。虽然Perl语言在半导体制造公司内具有更高的普及率,但是随着大数据和人工智能技术的不断发展,Python受到了越来越高的重视,因此我选择了Python作为数据处理的脚本工具。
平台:Windows系统,Spyder 3.1.4 软件,Python 3.6 语言。
本文主要内容:
1. 数据的读取与存储
2. 数据的清洗(预处理)
3. 数据的处理方法简介
我们拿到的数据或多或少都存在一些瑕疵和不足。多个数据可能存储在一个字符串中,也有可能分散在多个数据文件内,需要对数据进行拆分和组合。此外,数据还可能存在缺失、乱码、格式不统一等问题,这都需要对数据进行预处理。
本部分主要包括如下内容:
1. 字符串的拆分
2. 格式化字符串
3. 用正则表达式
re
库处理字符串数据
4. 在DataFrame中对数据进行预处理
5. 将各种数据结构转化为DataFrame
1. 字符串的拆分
用python内建的split( ) 函数可以对字符串进行拆分,并返回一个列表。如:
str = 'abc.edf'
temp = str.split('.') # 在出现“.”的位置对字符串进行拆分
print(temp)
输出结果如下:
在DataFrame中也可以实现字符串的拆分:
import pandas as pd
df = pd.DataFrame({"a" : ["1","2","3","4"],
"b" : ["5-9","6-10","7-11","8-12"]})
print(df)
print('----------')
# 将b按“-”拆分成两个字符串并分别存储在b和c中:
df['b'], df['c'] = df['b'].str.split('-', 1).str
print (df)
输出结果如图:
2. 格式化字符串
用内建的format函数可以实现字符串的格式化,其功能类似于C语言的print函数,只不过输出的结果保存在了一个字符串变量里。例如:
a = 12
b = 1
c = 'test'
str = format('%02d-%02d-%s' %(a, b, c))
上述代码的格式化输出结果为“12-01-test”:
用这个方法可以将不同格式的数据合并并统一起来。运用诸如 %02d 这样的语句,还可以在数据前自动补齐0,这可以解决很多数据合并时可能遇到的问题。
%之后可以跟随的内容主要包括:
类型代码:
%s 字符串(str()的显示)
%r 字符串采用repr()显示
%c 单个字符
%b 二进制整数 bin
%i 十进制整数 int
%o 八进制整数 oct
%x 十六进制整数 hex
%f 浮点数
%e 指数
%% 字符% (前提是里面要有格式符的话需要这么写)
关于对齐,可提供的值有:
+ 右对齐,整数前加正号,负数前加负号
- 左对齐,正数钱无符号,负数前加负号;
空格 右对齐;正数前加空格,负数前加负号
0 右对齐,正数前无符号,负数前加负号;用0填充
详细内容可参考:
%格式化和format格式化--python - CSDN博客
3. 用正则表达式
re
库处理字符串数据
正则表达式,又称规则表达式
。
(英语:Regular Expression,在python库中被称为re)。正则表达式通常被用来检索、替换那些符合某个模式(或者叫规则)的文本。
运用正则表达式,可以命令计算机执行诸如“分离出所有的邮编”、“分离出所有叫Ann的学生”等等命令,例如:
import re # 调用re库
str = 'abc1de23figh456kk0'
num = re.findall('\d+', str) # 寻找str中所有的数字及其1~n扩展
print(num)
输出的结果为:
常用的正则表达式如下:
. 任何单字符
[ ] 字符集
[^ ] 非字符集
* 前一字符0~n扩展
+ 前一字符1~n扩展
? 前一字符0~1扩展
L|R 左右表达式的任意一个
a{m} 扩展a字符m次
a{m,n} 扩展a字符m~n次
^ 匹配字符串开头部分
$ 匹配字符串结尾部分
( ) 分组标记,括号内只可使用"|"操作符
\d 数字0~9
\w 单字符A~Z加a~z加0~9
举例如下:
^[A-Z a-z]+$ # 26字母组成的字符串
^[A-Z a-z 0-9]+$ # 26字母加数字组成的字符串
^-?\d+$ # 整数字符串
^[0-9]*[1-9][0-9]*$ # 正整数字符串
[1-9]\d{5} # 中国大陆邮编
[\u4e00-\u9fa5] # 匹配中文字符
\d{3}-\d{8}|\d{4}-\d{7} # 国内电话号码
在re库中,常用的函数有:
import re # 载入re库
pattern = '\d+' # 正则式
string = '|5(5)|4(5)' # 原始字符串
# search:在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象
re.search(pattern, string, flags = 0)
# match:从开始位置搜索
re.match(pattern, string, flags = 0)
# findall:以列表形式返回全部匹配结果
re.findall(pattern, string, flags = 0)
# split:按结果分割并返回一个列表,maxsplit指最大分割数(匹配数))
re.split(pattern, string, maxsplit, flags = 0)
# finditer:循环用,每一个结果单独使用
re.finditer(pattern, string, flags = 0)
# sub:替换字符串
repl = 'FLAG' # 替换字符串
re.sub(pattern, repl, string, count = 0, flags = 0) # count指替换次数
要注意的是,re库在进行字符串匹配时会默认采用“贪婪匹配”,也就是说会返回符合条件的最长字符串。通过在操作符后增加一个“?”可以将正则式变成最小匹配:
match1 = re.search(r'PY.*N', 'PYANBNCNDN') # 贪婪匹配
match2 = re.search(r'PY.*?N', 'PYANBNCNDN') # 最小匹配
# 输出结果为:
# match1 = 'PYANBNCNDN'
# match2 = 'PYAN'
在半导体行业数据处理中,经常需要从一个字符串中读出数字信息,这时候采用正则表达式进行处理就会非常简单快捷。正则表达式在Perl、Office高级查询等文本处理工具中也有很广泛的应用,建议大家深入了解一下。这里推荐一个百度文库的链接:
python_re模块_百度文库
4. 在DataFrame中对数据进行预处理
DataFrame是一种存储结构化数据非常有力的方法,其内建的函数可以帮助我们快速而方便地对数据进行预处理。
4.1. DataFrame的基本操作
在数据预处理过程中涉及的DataFrame基本操作主要包括:
a. 行和列的添加与删除
b. 对DataFrame中指定位置数据的处理
c. 对DataFrame进行限定条件的筛选(类Database操作)。
a. 行和列的添加与删除
添加一列数据到DataFrame的方法很简单,直接以DataFrame索引的形式读取data数据即可。若DataFrame中不存在这个索引,会自动创建该索引并导入这一列数据。要注意的是,导入的数据长度和原数据长度应相同,否则会报错。如果读取的是一个数据(如一个数或者一个字符串),DataFrame会自动将这一列数据全部设为被读取的数据。
# 添加一列数据到DataFrame数据中:
num = [0, 1, 2, 3, 4]
df['data'] = num # 将num导入到df的data列,注意df和num的列长度应相同
info = 'RnD Data'
df['info'] = info # 每一行的info都会是“RnD Data”
通过append功能可以将两个相同结构的DataFrame拼接在一起:
df1 = df1.append(df2, ignore_index=True)
# 也可以写成:
df1.append(df2, ignore_index = True, inplace = True)
还可以以任意方式连接DataFrame的多个列,生成新的数据:
df['City'] + df['State'] # 连接两个series 类似于"123"+"abc" = "123abc"
df['Location'] = df['City'] + ", " + df['State'] # 自定义连接并赋值给新列
通过drop或者del功能可以删除指定的数据:
# 删除第1行和第2行的数据:
df.drop([1,2], axis = 0, inplace = True)
# axis = 1, drop列; axis = 0, drop行:
df.drop("string", axis = 1, inplace = true)
# drop掉title为City和State的列:
df.drop(['City', 'State'], axis = 1, inplace = True)
del df['data'] # 删除df中标签为data的列
pop方法可以将选中的数据提取出来,并且原DataFrame中也不再保留该列:
temp = df.pop['data'] # 删除df中的data列,并将data列的数据传递给temp
b. 对DataFrame中指定位置数据的处理
对指定位置数据的处理,重点在于如何指定位置。对DataFrame进行位置指定的方法主要有:
-
loc,基于列label和行的index选择数据;
-
iloc,基于行/列的position;
-
at,根据指定行index及列label,快速定位DataFrame的元素(只可选择一个数据);
-
iat,与at类似,不同的是根据position来定位的;
-
ix,为loc与iloc的混合体,既支持label也支持position。但是python3.6里已经不建议使用ix,最好还是采用loc或者iloc的方法。
print df.loc[1:3, ['total_bill', 'tip']]
print df.loc[1:3, 'tip': 'total_bill']
print df.iloc[1:3, [1,2]]
print df.iloc[1:3, 1:3]
print df.at[3, 'tip']
print df.iat[3,1]
print df.ix[1:3, [1,2]]
print df.ix[1:3, ['total_bill', 'tip']]
此外,有更为简洁的行/列选取方式:
print df[1: 3]
print df[['total_bill', 'tip']]
c. 对DataFrame进行限定条件的筛选(类Database操作)
按DataFrame的某些列进行排序:
Series的处理法:
df.title.sort_values( )
df['title'].sort_values( ) # 对Series进行sort处理,默认是正序
df['title'].sort_values( ascending = False ) # 逆序sort
df['title'].sort_values( ascending = True ) # 正序sort
DataFrame的处理法:
df.sort_values('title') # 对DataFrame按title列进行sort
多行排序:
df.sort_values(['title','name'])
按条件筛选数据:
# Method 1:
data = df[df.duration >= 200]
data = df[df.duration >= 200]['genre'] # 选择genre这一列,限定条件为duration>=200
# Method 2:
data = df.loc[df.duration >= 200, 'genre']
多条件筛选:
ufo[(ufo.duration >= 200) and (ufo.genre == 'Drama')]
还可以用
numpy
库的函数对DataFrame进行限定等:
import pandas as pd
import numpy as np
df = pd.DataFrame({'A:[1,2,3,4], 'B':[5,6,7,8], 'C':[1,1,1,1]})
df['D'] = np.where(df.A<3, 1, 0) # 如果A<3,D为1;如果A≥3,D为0
print(df)
输出结果为:
关于DataFrame的操作,我是在B站上看教学视频学习的。虽然是个英文的教学视频,但是语速比较低,难度不大。而且我个人认为讲的比许多中文教程要好很多,推荐大家看一看:
【全30集】使用 pandas 进行数据分析:Data analysis in Python with pandas
4.2. 数据缺失(NAN)的处理
缺失的数据在DataFrame中会被显示为“NAN”(Not a number)。针对不同的情况,可以有不同的解决方法。我在数据处理过程中主要遇到的情况为如下两种:
a. 数据缺失,该行数据需要被删除:
# 用dropna来删除数据
# inplace = True表示将填充后的结果覆盖原始的DataFrame
df.dropna(inplace = True)
b. 需要根据前序数据补齐内容:
# 对df中的空数据进行自动填充,填充方法:填充上一行数据
# inplace = True表示将填充后的结果覆盖原始的DataFrame
df.fillna( method = 'pad', inplace = True)
此外,fillna( ) 函数还有不同的method可选,包括:
df.fillna(method = 'bfill', inplace = True) # 用后一个值替代NAN
df.fillna(0) # 用0(也可以是任意一个数字)替代NAN
df.fillna('missing') # 用一个字符串替代NAN
在fillna( ) 中还可以嵌套调用 df 的一些属性。例如,可以用平均值代替NAN值:
df.fillna(df.mean()) # 用平均值代替NAN
还可以选择特定的列进行NAN值处理:
df.fillna(df.mean()['DUT':'CHIP'])
5. 将各种数据结构转化为DataFrame
DataFrame是一种非常好用的数据类型,推荐大家都在DataFrame中处理数据。这也将涉及到如何将各种数据转换为DataFrame,或者如何添加数据到DataFrame中。
5.1 字典转化为DataFrame
字典类型可以直接转换为DataFrame:
import pandas as pd
dic = {'one':['A','A','B','C','C','A','B','B','A','A'],
'two':['B','B','C','C','A','A','C','B','C','A'],
'three':['C','B','A','A','B','B','B','A','C','D']}
df = pd.DataFrame(dic)
print(df)
输出结果如图:
5.2 列表转化为DataFrame
DataFrame结构的数据具有列标签(label)、行标签(index)和数据内容。在转换成DataFrame的时候,系统会自动添加行标签,而列标签需要指定。可以采用如下代码添加标签:
import pandas as pd
x = [0, 1, 2, 3, 4]
y = [34, 54, 56, 76, 44]
df = pd.DataFrame({'x':x, 'y':y}) # 列表转换为DataFrame并添加标签
print(df)
输出结果如下:
多维列表可以直接转换为DataFrame,此时指定的label为默认值(0,1,2,……)。然后用 rename 功能来对label重命名:
import pandas as pd
x = [ [1,2,3,4], [5,6,7,8] ] # 包含两个不同的子列表[1,2,3,4]和[5,6,7,8]
df = pd.DataFrame(x) # 这时候是以行为标准写入的
print(df)
print('----------')
df.rename(columns={0:'a',1:'b'},inplace=True) # 注意label的0和1都不是字符串
print(df)
5.3 np.array 转化为DataFrame
np.array 型数据和列表数据类似,都是没有标签的数据类型,需要在转换的时候声明标签名称:
import pandas as pd
import numpy as np
s1 = np.array([1,2,3,4])
s2 = np.array([5,6,7,8])
df = pd.DataFrame({"a":s1,"b":s2})
print(df)
输出结果如下:
5.4 向已有DataFrame中添加数据:
添加新列的方法很简单,示例代码如下:
import pandas as pd
import numpy as np
df = pd.DataFrame({"A":[1,2,3,4],"B":[5,6,7,8],"C":[1,1,1,1]})
print(df)