日常业务需求中,仅凭SQL一招鲜是没法吃遍天的,这个时候就需要更为强大的Python进行支持了。这个系列主要分享一些Python小案例,都是根据笔者日常工作需求抽离总结的,如有雷同,纯属巧合~
这一期,主要是利用python处理非结构化文本数据。而且每个小案例可能隐藏着一些使用的Pandas
技巧.
隐藏知识点:函数递归
# ⚠️注意:用`json.loads`处理json型字符串时,键值应用双引号,外围用单引号。否则会转换失败,这里只是简单处理,所以采用`eval`函数避免转换错误。
# 随机构造一个json型的字符串
s = "{'A':111,'B':{'2':'b','1':{'a':23,'b':34,'c':{'HH':'good','hhh':[3,2,'q']}},\
'3':[1,2,3]},'C':['a',34,{'mm':567,'gg':678}]}"
# 开发半展开json函数
def json_half_flat(dic):
'''
半展开json,只对字典类型展开
dic:字典
return:展开后的字典
'''
init_dic = {}
keys = list(dic.keys())
for i in keys:
init_dic_value = dic[i]
if type(init_dic_value) == dict:
init_dic_value = json_half_flat(init_dic_value)
init_dic_keys = list(init_dic_value.keys())
for j in init_dic_keys:
name = str(i) + '_' + str(j)
init_dic[name] = init_dic_value[j]
else:
init_dic[i] = init_dic_value
return init_dic
# 开发全展开json函数
def json_flat(dic):
'''
全展开json,对字典、列表均进行展开
dic:字典
return:展开后的字典
'''
init_dic = {}
keys = list(dic.keys())
for i in keys:
init_dic_value = dic[i]
if type(init_dic_value) == dict:
init_dic_value = json_flat(init_dic_value)
init_dic_keys = list(init_dic_value.keys())
for j in init_dic_keys:
name = str(i) + '_' + str(j)
init_dic[name] = init_dic_value[j]
elif type(init_dic_value) == list:
length = len(init_dic_value)
for j in range(length):
name = str(i) + '_' + str(j)
init_dic[name] = init_dic_value[j]
else:
init_dic[i] = init_dic_value
return init_dic
s2j = eval(s)
json_half_flat(s2j) # 下方输出结果可以发现半展开json函数对列表或者列表内的json不做处理
{'A': 111,
'B_2': 'b',
'B_1_a': 23,
'B_1_b': 34,
'B_1_c_HH': 'good',
'B_1_c_hhh': [3, 2, 'q'],
'B_3': [1, 2, 3],
'C': ['a', 34, {'mm': 567, 'gg': 678}]}
s2j = eval(s)
json_flat(s2j) # 下方输出结果可以发现全展开json会对所有的json和列表均进行展开
{'A': 111,
'B_2': 'b',
'B_1_a': 23,
'B_1_b': 34,
'B_1_c_HH': 'good',
'B_1_c_hhh_0': 3,
'B_1_c_hhh_1': 2,
'B_1_c_hhh_2': 'q',
'B_3_0': 1,
'B_3_1': 2,
'B_3_2': 3,
'C_0': 'a',
'C_1': 34,
'C_2': {'mm': 567, 'gg': 678}}
这里介绍可用于处理中文地址的cpca
库,甚至还具有自动补全地址省市的功能。
隐藏知识点:列表列拆分为多列
pip install cpca
import pandas as pd
import numpy as np
import cpca
# 构造地址数据df
df_address = pd.DataFrame(
{'id':[1,2,3],
'address':["徐汇区虹漕路461号58号楼5楼", "泉州市洛江区万安塘西工业区", "北京朝阳区北苑华贸城"]})
df_address
id | address | |
---|---|---|
0 | 1 | 徐汇区虹漕路461号58号楼5楼 |
1 | 2 | 泉州市洛江区万安塘西工业区 |
2 | 3 | 北京朝阳区北苑华贸城 |
def get_address(location_str):
'''
提取字符串的地址信息
location_str:地址字符串
return:省、市、区、地址、邮编
'''
location_str=[location_str]
df = cpca.transform(location_str)
try:
p=df['省'].values[0]
c=df['市'].values[0]
d=df['区'].values[0]
ad=df['地址'].values[0]
adcode=df['adcode'].values[0]
except:
pass
return p,c,d,ad,adcode
df_address['local'] = df_address['address'].apply(get_address)
df_address[['province', 'city', 'district', 'address', 'adcode']] = df_address['local'].apply(pd.Series) # 列表拆分为多列
df_address
id | address | local | province | city | district | adcode | |
---|---|---|---|---|---|---|---|
0 | 1 | 虹漕路461号58号楼5楼 | [上海市, 市辖区, 徐汇区, 虹漕路461号58号楼5楼, 310104] | 上海市 | 市辖区 | 徐汇区 | 310104 |
1 | 2 | 万安塘西工业区 | [福建省, 泉州市, 洛江区, 万安塘西工业区, 350504] | 福建省 | 泉州市 | 洛江区 | 350504 |
2 | 3 | 北苑华贸城 | [北京市, 市辖区, 朝阳区, 北苑华贸城, 110105] | 北京市 | 市辖区 | 朝阳区 | 110105 |
这里通过urlextract
库进行url提取,并通过正则过滤非图片url
隐藏知识点:列转多行
# !pip install urlextract
import pandas as pd
import numpy as np
import re
# 构造地址数据df
df_pic = pd.DataFrame(
{'id':[1,2],
'content':[
'src=\"https://sm-wick-question.oss-cn-zhangjiakou.aliyuncs.com/QuestionAnswerImage/64809-1276-question.jpg\">
id | content | |
---|---|---|
0 | 1 | src="https://sm-wick-question.oss-cn-zhangjiak... |
1 | 2 | "avatar_url": "https://image.uc.cn/s/wemedia/s... |
# 提取url
def get_urls(s):
'''提取字符串的url
s:字符串
return:url列表
'''
from urlextract import URLExtract
extractor = URLExtract()
urls = extractor.find_urls(s)
return urls
# 列转多行
def split_row(df, column):
""" 拆分成多行
df: 原始数据
column: 拆分的列名
return: df
"""
row_len = list(map(len, df[column].values))
rows = []
for i in df.columns:
if i == column:
row = np.concatenate(df[i].values)
else:
row = np.repeat(df[i].values, row_len)
rows.append(row)
return pd.DataFrame(np.dstack(tuple(rows))[0], columns=df.columns)
# 提取urls
df_pic['urls'] = df_pic['content'].map(lambda x: get_urls(x))
# 列转多行
df_pic_result = split_row(df_pic, column='urls')
# 提取图片url
df_pic_result['pic'] = df_pic_result['urls'].map(lambda x: re.search('jpg|png', x, re.IGNORECASE))
df_pic_result = df_pic_result.dropna(subset=['pic']) # 删除没正则匹配到图片的数据
# 剔除尾部反斜杠、截取到jpg|png
df_pic_result['urls_new'] = df_pic_result['urls'].map(lambda x: re.search('http(.+?)(png|jpg)', x.rstrip('\\'), re.IGNORECASE).group(0))
# 展示结果
df_pic_result[['id', 'urls_new']]
id | urls_new | |
---|---|---|
0 | 1 | https://sm-wick-question.oss-cn-zhangjiakou.aliyuncs.com/QuestionAnswerImage/64809-1276-question.jpg |
1 | 1 | https://sm-wick-question.oss-cn-zhangjiakou.aliyuncs.com/QuestionAnswerImage/64809-1276-answer.jpg |
2 | 2 | https://image.uc.cn/s/wemedia/s/upload/2021/5850c345e69483fd27b2622e9216273f.png |
4 | 2 | http://image.uc.cn/s/wemedia/s/upload/2021/13078b26626a526e577585f6fc93430a.png |
利用正则过滤非中文,通过Python的Counter
进行统计。
import re
from collections import Counter
# 构造数据
s = ['asdk;*教师oq年教w%8青年教qw优秀asd;青年教师asd','教w%8青年教qw优秀a']
# 统计所有汉字
cn = Counter()
for i in s:
s_ch = re.sub('[^\u4e00-\u9fa5]+', '', i) # 剔除非中文
for j in s_ch:
if j=='':
continue
cn[j]=cn[j]+1
# 输出结果
print('统计结果:',list(cn.items())) # 查看统计结果
print('统计结果Top2:',cn.most_common(2)) # 查看次数最高的2个汉字
统计结果: [('教', 6), ('师', 2), ('年', 4), ('青', 3), ('优', 2), ('秀', 2)]
统计结果Top2: [('教', 6), ('年', 4)]
Hive也能完成上述任务,主要我在当时没想到split(str,‘’)可以进行分隔
-- Hive统计中文
with temp as
(
select 1 as id, 'asdk;*教师oq年教w%8青年教qw优秀asd;青年教师asd' as content
union all
select 2 as id, '教w%8青年教qw优秀a' as content
)
select
ch
,count(1) as cnt
from
(
select
id
,ch
from
(
select
id
,regexp_replace(content,'[^\\u4e00-\\u9fa5]', '') as s_ch
from
temp
)a
lateral view explode(split(s_ch,'')) t as ch
)a
where
ch != ''
group by
ch
背景:将汉字释义按照指定规则生成对应的json提供给研发。这个案例的可扩展性一般,主要分享如何用Ptyhon灵活处理复杂的数据需求。
隐藏知识点:df.at[index, col]
按照索引更新指定列的数值
import pandas as pd
import json
import re
df_sj = pd.DataFrame(
{'id':[1,2],
'char':['百','葡'],
'paraphrase':["1.数词,十个十。表示众多或所有的:百花齐放,百家争鸣|百战百胜。\n2.法定计量单位中十进倍数单位词头之一,表示10²,符号h。",
"[葡萄]落叶藤本植物。叶子呈掌状分裂,圆锥花序,茎有卷须,可缠绕其他物体,开淡黄色小花。果实为椭圆形或圆形,多汁,味酸甜,可食用或酿酒。"]})
for index, row in df_sj.iterrows():
paraphrase = row['paraphrase']
list_init=[]
for s in paraphrase.split('\n'):
dict_init={}
s=re.sub(r"(\d+\.)", "", s)
if ':' in s:
dict_init['value']=re.findall(r"(.*?):", s)[0]
dict_init['example']=re.findall(r":(.*)", s)[0].split('|')
else:
dict_init['value']=s
dict_init['example']=[]
list_init.append(dict_init)
df_sj.at[index, 'paraphrase_json']=json.dumps(list_init,ensure_ascii=False) # 根据索引修改列值
df_sj
id | char | paraphrase | paraphrase_json | |
---|---|---|---|---|
0 | 1 | 百 | 1.数词,十个十。表示众多或所有的:百花齐放,百家争鸣|百战百胜。\n2.法定计量单位中十进倍数单位词头之一,表示10²,符号h。 | [{"value": "数词,十个十。表示众多或所有的", "example": ["百花齐放,百家争鸣", "百战百胜。"]}, {"value": "法定计量单位中十进倍数单位词头之一,表示10²,符号h。", "example": []}] |
1 | 2 | 葡 | [葡萄]落叶藤本植物。叶子呈掌状分裂,圆锥花序,茎有卷须,可缠绕其他物体,开淡黄色小花。果实为椭圆形或圆形,多汁,味酸甜,可食用或酿酒。 | [{"value": "[葡萄]落叶藤本植物。叶子呈掌状分裂,圆锥花序,茎有卷须,可缠绕其他物体,开淡黄色小花。果实为椭圆形或圆形,多汁,味酸甜,可食用或酿酒。", "example": []}] |
本文主要介绍了利用Python处理文本数据,并穿插了一些Pandas小技巧
共勉~