虽然学pandas有段时间了,但是真正应用的场合其实很少,大多数功能用excel就已经足够,最近发现wps表格某些功能比 excel还好用,比如筛选重复身份证,wps表格就做得比excel2016要好。
最近发现了一个比较麻烦的问题,表格内容大概如下:
需求是把这些数据按村分类,听着是挺简单的,主要就是这个通讯地址数据不完整,存在以下问题:
1.有些地址没写村,如:护龙镇;
2.有些地址存在多个村,如:护龙镇木厂村护龙镇金盆村4组
3.有些地址中有空格,如:两板桥镇街 村1;
4.有些村沿用的是合并前的村名,如:鲤云村、聪明村现合并为聪明村
5.有些地址直接是空白,什么都没有;
我个人思考的结果是,尽量把所有数据都分到各村去,实现找不到村的数据,就划分到社区去。护龙镇只有1个社区(金盆村、长林社区合并为长林社区)。
import pandas as pd
import re
df = pd.read_excel("护龙-新冠疫苗第一针接种明细(截止9月6日).xls",header=1,index_col=0)
df2 = df.fillna("护龙镇")
这个其实很简单,就是如果地址为空,就修改为”护龙镇“。但是这种方法并不精确,把所有的非法字符都修改了,而且可能还有其它的情况,如:
大致可以采用以下的方式:
df2 = df.copy()
df2["通讯地址"] = df["通讯地址"].fillna("护龙镇")
#用正则表达式匹配,当然一般先单独测试筛选的数据是否准确,再做下列修改
df2.loc[df2["通讯地址"].str.match("护龙镇\d+"),"通讯地址"]="护龙镇"
虽然上面的方式并不通用,但是方式方法还是很可取的,不管遇见什么情况,都可以采取类似的方式。
将地址为”护龙镇“的数据,通过身份证比对户籍花名册,将此人的详细地址找出来,找不到的就还是默认为”护龙镇“。
户籍信息大体如下:
实现的步骤大体是,先判断地址是不是“护龙镇”,如果是就取出此行数据的“证件号码”,即身份证,然后再通过此身份证号码到户籍花名册中找到对面的行数据,然后再把此行数据的“村别”取出来。
df_hu = pd.read_excel("护龙镇户籍.xlsx",header=0)
def modify_address(sers):
address = sers['通讯地址']
idcard = sers['证件号码']
if(address == "护龙镇"):
dff=df_hu[df_hu['身份证']==str(idcard)]
if(dff.shape[0]>0):#有数据,就取出地址
return dff.iloc[0]['村别']
return address
df2['通讯地址'] = df2.apply(modify_address,axis=1)
上面代码的功能与 excel中的vlookup函数非常像,但是在pandas中要使用此功能并不容易,虽然细粒度的操作每条数据。上面代码中,用到了apply函数,axis参数=1的意思是将数据以行为单位,传递给modify_address函数。
df_hu[df_hu['身份证']==str(idcard)]
上面代码的意思就是通过身份证号码找到户籍花名册中,和这个身份证相等的那条记录,注意这记录是DataFrame,也就是说pandas并不确定找到的记录只有一条,所以要通过iloc方法来取:
dff.iloc[0]['村别']
最后返回的结果其实是一个Series,它由每一行数据修改后的地址合并而成。所以需要重新赋值给原DataFrame:
df2['通讯地址'] = df2.apply(.....)
上面代码可以再精简下,就是只传入通讯地址为“护龙镇”的数据:
def modify_address(sers):
address = sers['通讯地址']
idcard = sers['证件号码']
dff=df_hu[df_hu['身份证']==str(idcard)]
if(dff.shape[0]>0):#有数据,就取出地址
return dff.iloc[0]['村别']
return address
# df2['通讯地址'] = df2.apply(modify_address,axis=1)
df2.loc[df2['通讯地址']=="护龙镇","通讯地址"] = df2[df2['通讯地址']=="护龙镇"].apply(modify_address,axis=1)
虽然看起来要复杂一点,但是性能肯定会好很多。
每一个村都是三个字,即木厂村、新桥村等。
bool_cun = df2['通讯地址'].str.contains("聪明村|鲤云村|木厂村|遂安村|高棚村|高鹏村|新桥村|河堰村|夹石村|四新村|玉泉村|高红村|天堂村|天山村|羊角村|湾桥村|川七村")
df3 = df2[bool_cun]#取出为True的数据
如果一条记录的通讯地址包含了上面contains函数内的某一个村名,那么那条记录对应的数据就是True,否则为False,如下:
只取索引值为True的数据,这个确实有点意思,这种方式不像编程,有点像一种特有的规则。
def get_second_cun(s):#获得匹配到的第二个XX村
#\s是为了处理有空格的情况,如:两板桥镇街 村1
pattern = re.compile("[\w\s]{2}村")
result = pattern.findall(s)
return result[1]
list_cun = ["聪明村","鲤云村","木厂村","遂安村","高棚村","高鹏村","新桥村","河堰村","夹石村","四新村","玉泉村","高红村","天堂村","天山村","羊角村","湾桥村","川七村"]
def get_cun(df):#获得匹配到的第一个村别
s=df['通讯地址']
pos=s.find("村")
start=pos-2
end=pos+1
cun = s[start:end]
#如果前面匹配到的不是想要村别,比如:护龙镇金盆村护龙镇木厂村,取第匹配到的第二个村别
if(cun not in list_cun):
cun = get_second_cun(s)
#如果遇见合并后已弃用的村,就返回合并的村别
if(cun=="鲤云村"):
return "聪明村"
elif(cun=="高棚村"or cun=="高鹏村"):
return "遂安村"
elif(cun=="河堰村"):
return "新桥村"
elif(cun=="四新村"):
return "夹石村"
elif(cun=="高红村"):
return "玉泉村"
elif(cun=="天山村"or cun=="羊角村"):
return "天堂村"
elif(cun=="川七村"):
return "湾桥村"
return cun
df3 = df2[bool_cun]
df4=df3.copy()#用副本操作修改数据,不然有时会报警告
df4.loc[:,"合并村"] = df3.apply(get_cun, axis=1)
上面的代码注释得还算详细,讲得重要的,比如查找的数据肯定包含下列村中的一个:
"聪明村|鲤云村|木厂村|遂安村|高棚村|高鹏村|新桥村|河堰村|夹石村|四新村|玉泉村|高红村|天堂村|天山村|羊角村|湾桥村|川七村"
但是有个潜在的问题,那就是我们默认是取第一匹配到的村别,如果社区合并前的村(金盆村)而包含在通讯地址中,且排在前面,如:护龙镇金盆村护龙镇木厂村,那么我们取到的就会是金盆村,所以要判断:
if(cun not in list_cun):
cun = get_second_cun(s)
如果不包含,就再取一次,取匹配到的第二个村别。
#社区1468
df5 = df2[~bool_cun]
df6 = df5.copy()
df6['合并村'] = "长林社区"
直接加一列“合并村”,并赋值为“长林社区即可”。
df4.to_excel('3.xlsx')
df6.to_excel("4.xlsx")
上面代码非常简单,把处理好的数据储存为excel。
上面添加的“合并村”,默认是添加到最后一列的位置,我们也可以把它放在前面点,比如第二列的位置:
#虽然下面的df4.loc[:,"合并村"]可以直接增加列,但是默认是增加到最后面的一列
#下面这三行代码的作用是将“合并村"增加到指定位置,即第2列
col_name=df4.columns.tolist()
col_name.insert(0,"合并村")
df4=df4.reindex(columns=col_name)
df4.loc[:,"合并村"] = df3.apply(get_cun, axis=1)
得到的结果大体如下:
上面代码所实现的功能,在 excel或者wps表格中都不可能实现,当然可以使用宏编程,但是结果应该要比这个复杂得多。因为python比VBA代码更精简、友好,再加上pandas这种专门操作表格的框架,所以代码精简了很多。