微信号:shushuojun

介绍:数据分析师之家.旨在为数据人提供一个学习、分享、互帮互助的家园.

统计师的Python日记【第九天:正则表达式】

2016-06-15 19:20 数说君

本文是【统计师的Python日记】第9天的日记


回顾一下:


  • 第1天学习了Python的基本页面、操作,以及几种主要的容器类型。

  • 第2天学习了python的函数、循环和条件、类。

  • 第3天了解了Numpy这个工具库。

  • 第4、5两天掌握了Pandas这个库的基本用法。

  • 第6天学习了数据的合并堆叠。

  • 第7天开始学习数据清洗,着手学会了重复值删除、异常值处理、替换、创建哑变量等技能。

  • 第8天接着学习数据清洗,一些常见的数据处理技巧,如分列、去除空白等被我一一攻破


原文复习(点击查看):


【第1天:谁来给我讲讲Python?】


【第2天:再接着介绍一下Python呗】


【第3天:Numpy你好】


【第4天:欢迎光临Pandas】


【第四天的补充


【第5天:Pandas,露两手】


【第6天:数据合并】


【第7天:数据清洗(1)】


【第8天:数据清洗(2)文本处理】


今天将带来第9天的学习日记。


目录如下:


前言

1. 正则表达式简介

(1)元字符

(2)函数

2. 用正则表达式处理Pandas数据

(1)匹配行

(2)提取匹配文字

(3)提取匹配文字的一部分




统计师的Python日记【第9天:正则表达式】


前言


根据我的Python学习计划:


Numpy → Pandas 掌握一些数据清洗、规整、合并等功能掌握正则表达式 掌握类似与SQL的聚合等数据管理功能 → 能够用Python进行统计建模、假设检验等分析技能 → 能用Python打印出100元钱 → 能用Python帮我洗衣服、做饭 → 能用Python给我生小猴子......


在数据清洗的学习过程中,发现文本数据的处理并非一招半式能解决,有时必须要搬出利器——正则表达式。在之前的【SAS正则表达式】系列中(在后台回复【sasre】查看),我用正则表达式做文本处理做的非常之爽,比如下面这列数据:


(01)1872-8756

Body shop P1

Book B13

(05)9212-0098

PD(05)9206-4571

Shushuophone

(12) 6753-5513

None here

PD(12)6434-4532

P&DWashing

......(未显示完)


这是一份产品名单,有的用数字来编码,有的直接是产品的名字,现在想把数字编码(也即红色字体)的部分提取出来,看似没有什么规律,但是在SAS中,用正则表达式两行代码就搞定了。


现在,要挑战用正则表达式处理Pandas的数据。



1. 正则表达式简介


虽然在SAS中学了正则表达式的基础,Python稍有不同,现在还是简单复习一下:


(1)元字符


元字符是一系列代码,用来化表达某种意思,比如:

\d 表示数字

\D 表示非数字

\w 表示单词字符

\W 表示非单词字符

等等。


有一个技术博客里给了很好的总结,网址:http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html,以备查询。


(2)函数


在SAS中,PRXPARSE()是获取一个正则表达式的pattern,在Python中对应的就是 compile()


import re

pattern = re.compile(元字符表达式)


比如说,


  • pattern = re.compile('\d') 就是把一个数字存到pattern里了;

  • pattern = re.compile('[0-9]') 也可以用这个,表示把0-9任意一个存到pattern里去;

  • pattern = re.compile(',') 就是把逗号存到pattern里去。


那它有什么用呢?pattern后面可以接函数,来实现一些文本处理的功能,比如:


  • pattern.split(text) 对text按照pattern分割成list;

  • pattern.findall(text) 匹配text中所有符合pattern的部分;

  • pattern.search(text) 匹配text中第一个符合pattern的部分;

  • pattern.match(text) 匹配text开头符合pattern的部分;

  • pattern.sub(subtext, text) 将text中符合pattern的部分替换为subtext。


有点抽象,来具体学习一下,以text为例:


text="Shu Shuojun, I love u, 520"


这个文本,如果想将它变成一个list,用逗号来分割,我可以这样:


pattern = re.compile(',')

pattern.split(text)


结果为:



而利用 findall(),我可以寻找某种格式的字符,相当于SAS中的PRXMATCH(),比如想找到以Sh开头的字符:


pattern = re.compile('Sh\w*')

pattern.findall(text)


\w表示单词字符,*表示匹配前面的表达式0次或无限次,\w*也就是匹配一个单词0次或无限次,'Sh\w*'这个元字符的意思就是:匹配以Sh开头,后面跟着N个单词字符的文本(N取0到无穷)




Sh开头的两个单词都被匹配出来了。


search() 跟findall类似,findall返回的是字符串中所有的匹配项,search则只返回第一个匹配项的起始位置和结束位置相当于SAS中的PRXSUBSTR(),同样以刚才的例子,换成search()看看会发生什么变化:


pattern = re.compile('Sh\w*')

pattern.search(text)




search()返回的是起始位置和结束位置,分别记录在这个东东的.start()和.end()两个函数里面,因此要这样:


pattern = re.compile('Sh\w*')

m = pattern.search(text)

print text [ m.start() : m.end() ]




所以search()只记录了第一个匹配项的开头和结束位置。


还有一个函数 match(),与search()不同之处在于,它只匹配字符串的开头部分




从这里看与search没什么差别,因为text的开头就是Shu,如果换一下只匹配Shushuo看看,也就是pattern改成:pattern = re.compile('Sh\w\w+')


 

用search()完美匹配出来了,+表示匹配前面的字符至少一次。用match()呢?




不行,匹配不出来,因为Shuojun不是出现在开头。

 

sub() 方法是用来替换,SAS中的PRXCHANGES也提供了替换,比如现在想把text中的520换成250


pattern = re.compile('\d+')

pattern.sub('250',text)


\d表示数字字符,\d+表示匹配数字字符至少1次,由于text中的数字只有520,因此,text中符合pattern的必然是520这部分。


pattern.sub('250',text)就是把text中520换成250:




在SAS中,学过 “打包”




在Python的正则表达式也可以“打包”,比如将”I love shushuo”中的shu和shuo分别打包:


text = 'I love shushuo'

pattern = re.compile( '(shu)(shuo)' )

m = pattern.search(text)

m.groups()




再比如,将ve和shuo打包:




正则表达式是文本分析的利器,在爬虫中用处也非常大。但本文中,我要挑战的是对DataFrame结构数据进行正则表达式的处理。参照SAS正则表达的介绍,试图将在SAS中实现的功能在Python中也能实现。



2. 用正则表达式处理Pandas数据


(1)匹配行


我在SAS中用正则表达式解决的第一个问题是是这样的:


(01)1872-8756

Body shop P1

Book B13

(05)9212-0098

PD(05)9206-4571

Shu shuo phone

(12) 6753-5513

None here

PD(12)6434-4532

P&DWashing

......(未显示完)

 

也就是开头的问题,这一份产品列表,现在只想要数字编码、也就是红色字体的部分。如何操作?


先来分析一下:

 

首先两个PD不是必须的,有的有、有的没有,但后面(XX)括号里面两个数字是必须的,我就按照这样的模式来获取红色字体部分:

 

pattern = re.compile('P?D?\D\d{2}\D\s?\d{4}-\d{4}')

 

这个表达式如何匹配的?


编号

P

D

(

XX

)

空格

XXXX

-

XXXX

正则表达式

P?

D?

\D

\d{2}

\D

\s?

\d{4}

-

\d{4}

对于单个字符串很简单,findall一下就可以了,正如第一部分的介绍,但是对于DataFrane的数据结构,该如何实现?


先读入Pandas中去,数据就命名为production:




我捣鼓出了两种方法:


方法一:


pattern = re.compile('P?D?\D\d{2}\D\s?\d{4}-\d{4}')


matchPro = [] #用来储存匹配的观测值


for i in production['text']: #进行逐行匹配

    if re.findall(pattern, i): #判断是否匹配

        matchPro.append(i) #如果匹配了就把这个观测值放进matchPro中去

        

pd.DataFrame(matchPro, columns=['text']) #最终生成匹配出来的DataFrame数据。




成功匹配出来了。


方法二:


思路是将匹配行的索引记录下来,而不是观测值:


pattern = re.compile('P?D?\D\d{2}\D\s?\d{4}-\d{4}')

 

delIndexSet = [] #用来储存匹配行的索引

 

for i in production['text']: #逐行匹配

   if re.findall(pattern, i): #是否匹配

       delIndex = list(production['text']).index(i) #如果匹配了就获取这行的索引

       delIndexSet.append(delIndex)  #将匹配行的索引放进delIndex

       

pd.DataFrame(production,index=delIndexSet) #获取原数据中的匹配行


也可以成功匹配出来。




(2)提取匹配文字


在SAS正则表达式中还遇到了新的问题:


(01)1872-8756

Body shop P1

Book B13

(05)9212-0098

PD(05)9206-4571

Shushuo phone

(12) 6753-5513

None here

PD(12)6434-4532

P&D Washing

PC Pro4321S: (09) 1352-3154


这是一份新的产品列表,现在多了最后一行,这一行是产品的名字和数字编码放在一起了,我只想要数字编码的部分,即红色部分,前面的不想要,怎么办?


第一部分中介绍了search()提取了匹配部分的开头和结尾部分,这个一定可以帮我解决!


先把数据读入Pandas,仍然命名为production:




解决代码如下:


pattern = re.compile('P?D?\D\d{2}\D\s?\d{4}-\d{4}')

 

matchPro = [] #将匹配部分的文字装入这个list

 

for i in production['text']: #逐行匹配

   if re.search(pattern, i):

       m = re.search(pattern, i)

       matchText = i[m.start():m.end()] #如果匹配,那么利用匹配部分开头和结尾的位置,来获取匹配的字符

matchPro.append(matchText) #装入matchPro

 

pd.DataFrame(matchPro, columns=['text'])


结果如下:





(3)提取匹配文字中的一部分


刚刚对于这个例子:

 

(01)1872-8756

Body shop P1

Book B13

(05)9212-0098

PD(05)9206-4571

Shushuo phone

(12) 6753-5513

None here

PD(12)6434-4532

P&D Washing

PC Pro4321S: (09) 1352-3154

 

我成功的写了一个正则表达式,提取出来匹配的部分,元字符为:


P?D?\D\d{2}\D\s?\d{4}-\d{4}


这个表达式和红色字体部分是对应的。那么有一个问题,假如我想提取出来这段匹配文字的任一部分呢?比如(09) 1352-3154这个括号里的数字,按照情节设定,括号里的数字代表产品的类型,现在想把它提取出来。


和SAS一样,同样用“打包”的思路,前面已经学过在Python中如何打包了:


pattern = re.compile('P?D?\D(\d{2})\D\s?\d{4}-\d{4}') #将括号里的数字“打包”

matchType = []


for i in production['text']:

   if re.search(pattern, i):

       m = re.search(pattern, i)

       type = m.groups()[0] #打包后的内容存在groups()中

       matchType.append(type)

   

pd.DataFrame(matchType,columns=['type'])   




哎呀,只有一列,我不知道每个数字跟原来的哪个对应啊,我得把原数据也加上:


pattern = re.compile('P?D?\D(\d{2})\D\s?\d{4}-\d{4}')

matchPro = []

matchType = []

match = {}

 

for i in production['text']:

   if re.search(pattern, i):

       m = re.search(pattern, i)

       type = i[m.start():m.end()]

       matchText = m.groups()[0]

       

       matchType.append(type)

       matchPro.append(matchText)

   

match['text'] = matchPro

match['type'] = matchType

 

pd.DataFrame(match,columns=['text','type']) 


这样,原数据也有,打包的部分也有了,结果如下:





(4)总结


虽然具体的问题千奇百怪,但核心的方法都是一样的,正则表达式函数+迭代 = Pandas数据的处理。考验的还是Python技巧的综合运用。




做个小游戏,您觉得本【统计师的Python日记】系列如何?


1、不好——跳转至A


2、好——跳转至2.1


2.1 打赏吗?

打赏——跳转至B

不打赏——跳转至2.2


2.2 点击文末广告?

点击——跳转至C

不点击——跳转至A


A 有什么建议意见呢?您可以在文末评论区留言,帮我做的越来越好!

B 谢谢爷~!劳驾您在文末打赏,我会再接再厉哒!

C 谢谢小哥,谢谢美女~!广告商会给我打赏哒!




1. 授权白名单请给数说君留言


2. 关于数据分析的提问求助,可以在分答中搜索“数说君”找我


3. 投稿、免费发布招聘、合作,请加数说君个人微信AnselT,或Email:jiayounet@163.com。


4. 查看所有文章请在后台回复【M】




数说工作室,数据中看大势

微信ID:shushuojun


长按二维码关注数说工作室

 
数说工作室 更多文章 余额宝为什么不该被取缔? 盈余管理的测量 量化投资之多因子选股模型 金融数据挖掘之决策树(ID3) 量化投资之动量反转
猜您喜欢 阿里资深前端工程师桐木:站在10年研发路上,眺望前端未来 文本自动分类案例(源码) Weex——关于移动端动态性的思考、实现和未来 程序员,为何你不该加班? 最美应用-从Android研发工程师的角度之[厨房故事]