全国三甲医院分析-Pyecharts自定义坐标(json_file)

为pyecharts定制json文件,自定义坐标

虽然Pyecharts很多槽点,但是目前还没找到替代品,换新工具也有时间成本(沉没成本?)。之前爬虫爬取的全国三万家医院信息放在了mysql,最近在休假正好有空拿来处理下做个分析。这两天主要是学习下json包的使用,因为pyecharts的自定义坐标用json文件比较方便:

从SQL读取并将关键信息写入新的sql table

def generate_mysql():
    conn = pymysql.connect(
        host='localhost',
        user='username',
        password='passwd',
        port=3306,
        charset = 'UTF8MB4',  
        db = 'hospital_list')
    cursor = conn.cursor()

    sql = 'CREATE TABLE IF NOT EXISTS hospital_coord (hospital VARCHAR(30),\
    provinces VARCHAR(15), cities VARCHAR(15), location VARCHAR(30) ,phone VARCHAR(30), PRIMARY KEY (hospital))'
    # hospital_list是要在数据库中建立的表,用于存放数据
    
    cursor.execute(sql)
    conn.close()
def read_mysql(db):
    Engine = create_engine('mysql+pymysql://user:[email protected]:3306/{0}?charset=UTF8MB4'.format(db))
    con = Engine.connect()
    #提取hospital_name_new, Provinces, Cities, address, phone
    df = pd.read_sql("""SELECT hospital_name_new, Provinces, Cities, address, phone
    FROM top_tier3_hospital ORDER BY Cities ASC """, con)
#    print(type(df['Cities', 'address']))
#    print(df['address'])
    for m in range(0, len(df[ 'address'])):
        df[ 'address'][m] = str(df[ 'address'][m]).split()[0]
        if(len(str(df[ 'address'][m]).split()) > 1):
            print(df[ 'address'])
    
    return df['hospital_name_new'], df['Provinces'], df['Cities'], df[ 'address'], df['phone']
    con.close()
#hospital, provinces, cities, location, address, phone 
        
def write_to_sql(data_frame, db = 'hospital_list'):
    engine = create_engine('mysql+pymysql://user:[email protected]:3306/{0}?charset=UTF8MB4'.format(db))
    try:
        # df = pd.read_csv(df)
        data_frame.to_sql('hospital_coord',con = engine,if_exists='append',index=False)
        # append表示在原有表基础上增加,但该表要有表头(header)
    except Exception as e:
        print(e)       
      

这部分比较熟练了,但是还是有几点需要注意:

  • primary key的使用,防止重复数据
  • 处理不规范数据,比如之前地址栏有两个地址(分号隔开),医院名称也有别称(括号内),个别医院地址找不到返回的无效坐标(0,0)-这个会在后面的程序中带来很多麻烦,比如重复数据,None类型的这种数据会带来意外的问题。
  • SQL命令熟悉了可以方便查看一些数据问题,否则还得写代码,即使是python也很浪费时间,不如命令行来的方便。比如这次产生的None类型数据,重复医院名称,很容易在命令行查到,如果用Python大概率要写代码循环判断。
  • 常用的几个命令:
SELECT * FROM top_tier3_hospital WHERE hospital_name_new LIKE '%枣庄%' and hospital_tier LIKE '三级甲等%';
SELECT COUNT(*) FROM hospital_coord;
SELECT * FROM top_tier3_hospital WHERE address LIKE "%;%";
SELECT * FROM top_tier3_hospital LIMIT 20;

mysql>  SELECT * FROM hospital_list_table
    ->  WHERE hospital_name_new='南京长江医院第三医院' or  hospital_name_new='孝感市人民医院';
数据查重:
mysql>  SELECT COUNT(*) as repetitions, hospital_name_new
    ->  FROM hospital_list_table
    -> GROUP BY hospital_name_new
    ->  HAVING repetitions > 1;

xml转json:

  • 百度API返回的页面没有xml的declaration,但是Python中requests的response却是有的,这个declaration最好去掉:
  • 百度的返回:
<GeocoderSearchResponse>
<status>OK</status>
<result>
<location>
<lat>33.082054</lat>
<lng>107.022983</lng>
</location>
<precise>1</precise>
<confidence>80</confidence>
<level>门址</level>
</result>
</GeocoderSearchResponse>
#定义xml转json的函数
def xmltojson(xmlstr):
    #parse是的xml解析器
    xmlparse = xmltodict.parse(xmlstr)
    #json库dumps()是将dict转化成json格式,loads()是将json转化成dict格式。
    #dumps()方法的ident=1,格式化json
    jsonstr = json.dumps(xmlparse,indent=1)
    return jsonstr
    #print(jsonstr)

百度API调取坐标

这个函数是用来从百度调取具体地点的坐标的,申请API之后就有了这个ak密钥。虽然百度限制宽松,但是我还是限制了使用频率,所以程序会跑300多秒。另外,加上headers和except容错机制,不然肯定会有几个地点让程序崩溃。需要说明的是,ExpatError这个是需要在程序开头import相关的包的,用了捕捉出现的错误,一个健壮的程序必不可少。

容错语句里我傻傻的加上了错误的地方返回(0,0),其实这个后来给我带来了麻烦。我没有重写这段,只是在后来把产生的(0,0)去掉了。不过,不返回一个值我觉得会有问题,给它一个确定的返回值更好,后面也很方便处理掉,因为这个是可控的。

def __get_location1__(hospital, provinces, cities, location_list, address, phone):  
        my_ak = 'yourak_from_baidu'    # 需要自己填写自己的AK
#        print(location_list)
        headers = {
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
            "Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": "zh-CN,zh;q=0.8",
            "Cache-Control": "max-age=0",
            "Connection": "keep-alive",
            "Upgrade-Insecure-Requests": "1",
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36"
        }
        try:
            url = 'http://api.map.baidu.com/geocoder?address=' + location_list +'output=json&key=' + my_ak
            print(url)
            response = requests.get(url, headers = headers , timeout = 15)
            txt = response.text
#            print(txt)
            print('\n')
            res_json = txt[42:]
            res_json_ = xmltojson(res_json)
            print(res_json_)
            json_dict = json.loads(res_json_)
#            print(type(json_dict))
            print(json_dict)
            lat = json_dict['GeocoderSearchResponse']['result']['location']['lat']
            lng = json_dict['GeocoderSearchResponse']['result']['location']['lng']
            print(lat, lng)
#            xml_dict = xmltodict.parse(res_json)
            return hospital, provinces, cities, lat, lng, address, phone      
            
        except ExpatError as e:
            print('location {} {} not found. BaiduMap is lost!'.format(cities, location_list))
            print(e)
            #print(response.status_code)
            print(time.strftime('%Y-%m-%d %H:%M:%S'))
#            time.sleep(random.random()*2)
            return 0,0

坐标转换成Geo需要的json文件(自定义坐标)

自定义坐标这块pyecharts官方文档没有详细说明,网上也很少相关的帖子。Geo对json格式要求严格,对输入的data_pair要求也很高,不然不会出图。先说data_pair:

data_pair 我通常用list(zip(list1, list2)),这样你就得到了a list of tuples,比如:

[('重庆三峡中心医院平湖分院', '重庆市', '万州区', '30.836284', '108.409686', '重庆市万州区北山大道862号', '023-58355555,023-58355511'), ('重庆三峡中心医院御安分院', '重庆市', '万州区', '30.844058', '108.425126', '重庆市万州区北山大道1666号', '023-58339601,023-58339553'), ('重庆三峡中心医院百安分院', '重庆市', '万州区', '30.771119', '108.452463', '重庆万州区五桥百安移民新区', '023-58556222')]

通常,你可能不需要这么多,只是[(‘name’, ‘value’), (‘name2’, ‘value2’) ] 就够用了,但是我这里需要在地图上显示医院名称,详细地址和电话。而Geo函数所需要的坐标是自定义json文件中得到的。Pyecharts自带的city_coordinate_json是这个样子的,你的格式也必须处理成这样。这里你会用到json_dumps, json_loads。

{
  "阿城": [
    126.58,
    45.32
  ],
  "阿克苏": [
    80.19,
    41.09
  ],
  "阿勒泰": [
    88.12,
    47.5
  ]
}

python, json, xml的转换

今天在做数据处理时用到了百度地图API,结合Geopy来做地点的定位,当然需要坐标啦。

可惜的是,作为新手我没注意到百度返回的是xml,需要转换成json,然后在转换成Python里的字典(dict),方便嵌套引用。百度返回的是个这样的字符串,但是打开url返回的没有第一行header。开始我以为是header问题,还把第一行去掉了,其实是多此一举。

<?xml version="1.0" encoding="utf-8" ?> 
<GeocoderSearchResponse> 
	<status>OK</status>
	<result>
					<location>
				<lat>37.195699</lat>
				<lng>122.096751</lng>
			</location>	
			<precise>0</precise>
			<confidence>30</confidence>
			<level>道路</level>
			</result>	
</GeocoderSearchResponse>
<GeocoderSearchResponse> 
	<status>OK</status>
	<result>
					<location>
				<lat>37.195699</lat>
				<lng>122.096751</lng>
			</location>	
			<precise>0</precise>
			<confidence>30</confidence>
			<level>道路</level>
			</result>	
</GeocoderSearchResponse>

这个xml可以尝试在 https://www.json.cn/# 进行转换(转换成json的dict格式),如果成功说明数据没问题。在python里可以用xmltodict这个第三方包来把xml转换成json(到了这里离我们所需要的Pythondict就是咫尺之遥了):

{
 "GeocoderSearchResponse": {
  "status": "OK",
  "result": {
   "location": {
    "lat": "37.195699",
    "lng": "122.096751"
   },
   "precise": "0",
   "confidence": "30",
   "level": "\u9053\u8def"
  }
 }
}

最后,需要把上面的json用json.loads转换成python里的dict:

{'GeocoderSearchResponse': {'status': 'OK', 'result': {'location': {'lat': '37.195699', 'lng': '122.096751'}, 'precise': '0', 'confidence': '30', 'level': '道路'}}}

到了这里你就能方便地索引了,比如索引这里的lat(dict就是层层嵌套):

print(json_dict['GeocoderSearchResponse']['result']['location']['lat'])

项目完成后再把源码放到github上,参考:

https://blog.csdn.net/qq_39247153/article/details/81984524

https://www.cnblogs.com/chenfulin5/p/7834466.html

(json的用法)

https://blog.csdn.net/JOJOY_tester/article/details/71435935 (介绍xml2dict)

跑代码的时候又发现数据的另外一个问题,个别医院的地址栏竟然有两个地址,分号分开的!程序就崩溃了。如下:

mysql> SELECT * FROM top_tier3_hospital WHERE address LIKE "%北京市东城区天坛西里4号%";
+------------------------------+---------------+-----------+--------+---------------------------------------------------------------------+----------------------------------------+
| hospital_name_new            | hospital_tier | Provinces | Cities | address                                                             | phone                                  |
+------------------------------+---------------+-----------+--------+---------------------------------------------------------------------+----------------------------------------+
| 首都医科大学附属北京口腔医院 | 三级甲等      | 北京市    | 东城区 | 北京市东城区天坛西里4号(天坛);北京市东城区锡拉胡同11号(王府井) | 010-67099114(总机);67099284(咨询) |
+------------------------------+---------------+-----------+--------+---------------------------------------------------------------------+----------------------------------------+
1 row in set (0.00 sec)

SQL查重和防止数据重复

今天从爬下来的医院数据创建一个新数据表时遇到了错误,这个也是预料中的,因为爬取的医院名称有的在括号内还有一个名称,我处理数据时把括号及里面的数据删除掉了,所以结果肯定有少量重复。解决办法是重新处理数据写入SQL,重新设置primary key

mysql> CREATE TABLE tier3_hospital (UNIQUE (hospital_name_new)) SELECT hospital_name_new,hospital_tier,Provinces,Cities,address,phone FROM hospital_list_table WHERE hospital_tier LIKE “%三 级%”;
ERROR 1062 (23000): Duplicate entry ‘吉林大学第三医院’ for key ‘hospital_name_new’
mysql> SELECT COUNT(*) as repetitions, hospital_name_new
-> FROM hospital_list_table
-> GROUP BY hospital_name_new
-> HAVING repetitions > 1;
+————-+———————————-+
| repetitions | hospital_name_new |
+————-+———————————-+
| 2 | 中德精神康复医院 |
| 2 | 兴义市人民医院 |
| 2 | 内蒙古胸科医院 |
| 2 | 南京市长江医院第四医院 |
| 2 | 南京长江医院第三医院 |
| 2 | 南京长江医院第二医院 |
| 2 | 吉林华侨医院 |
| 2 | 吉林大学第三医学院分院 |
| 2 | 吉林大学第三医院 |
| 2 | 呼伦贝尔盟第二人民医院 |
| 3 | 唐山华新纺织集团医院 |
| 2 | 四川省中医药研究院中医医院 |
| 2 | 太原市小店区亲贤医院 |
| 2 | 太原市煤炭气化公司职工医院 |
| 2 | 孝感市人民医院 |
| 2 | 安丘市白求恩医院 |
| 2 | 安丘市精神卫生中心 |
| 2 | 安溪县中医院 |
| 2 | 宜宾市康复医院 |
| 2 | 广州市番禺区岐山医院 |
| 2 | 广西亭凉医院 |
| 2 | 广西龙泉山医院 |
| 2 | 张家口市肺科医院 |
| 2 | 本溪钢铁公司胸科医院 |
| 2 | 梧州市里湖医院 |
| 2 | 武汉钢铁公司第二职工医院 |
| 2 | 武汉钢铁公司第二职工医院结核病院 |
| 2 | 河北省中医院 |
| 2 | 淮南朝阳医院 |
| 2 | 牡丹江市结核医院 |
| 2 | 苏州九龙医院 |
| 2 | 襄阳汽车轴承股份有限公司医院 |
| 2 | 镇江市烧伤医院 |
| 2 | 长治市商业医院 |
+————-+———————————-+
34 rows in set (0.04 sec)

下面这个情况就很尴尬了:

mysql> SELECT * FROM hospital_list_table
-> WHERE hospital_name_new=’吉林大学第三医院’;
+———————————————-+——————-+————————-+———–+——————+—————+——————————-+————–+——-+———+
| hospital_name | hospital_name_new | city | Provinces | Cities | hospital_tier | address | phone | email | website |
+———————————————-+——————-+————————-+———–+——————+—————+——————————-+————–+——-+———+
| 吉林大学第三医院 | 吉林大学第三医院 | 吉林省-延边朝鲜族自治州 | 吉林省 | 延边朝鲜族自治州 | 三级甲等 | 吉林省长春市二道区仙台大街2号 | 0431-4646331 | NULL | NULL |
| 吉林大学第三医院(中日联谊二部)(吉大三院) | 吉林大学第三医院 | 吉林省-长春市 | 吉林省 | 长春市 | 三级甲等 | 吉林省长春市仙台大街2号 | 0431-5656921 | NULL | NULL |
+———————————————-+——————-+————————-+———–+——————+—————+——————————-+————–+——-+———+
2 rows in set (0.02 sec)

http://huanyouchen.github.io/2018/05/22/mysql-error-1406-Data-too-long-for-column/

另外,mysql报错:1406, “Data too long for column”,可以:

在MySQL中设置这个:

mysql> SET @@global.sql_mode= ”;

python入门经验贴

这段话发给朋友的,码了自己的心路历程。

你的学习能力远高于我,我建议你从python开始,掌握基本语法后尝试简单的数据分析和办公自动化,不然脱离了实际工作你很快就忘记了。工作中一些简单重复的paperwork完全可以考虑python,比如合并批量excel表格,更新模板类的ppt(比如每次只需要更新图片,我就做了个脚本更新周报)。
sql上手快,但是很多命令如果不常用忘记很快。主要就是查改增删,高大上的你没有机会用。
关系型数据库特别适合存放大数据,比如几十万行的数据表,超过百万excel就无能为力了,太慢(比如我常见的各种导航数据,很多项目都是百万炮,excel处理非常不方便)。总之要结合工作需求,学习快也更有动力。
入门我用的比较经典的learn python the hard way(短时间内给你个编程的感觉),敲完这本书后接着学习基本语法,我用的foundations for data analytics,介绍的基本语法,处理文本,csv,excel,sql等,因为这些我经常用。
然后我就开始了python for data analysis,这次休假我就能刷完了,500页左右。必须手动敲代码,不然会眼高手低。其实这个阶段我已经能写简单的脚本了,在船上工作不忙,三周左右我做了五个小项目,每天持续工作十几个小时,ppt周报自动化,爬虫肺炎疫情/全国医院数据/全国的A股上市公司数据,代码也打了几千行(有些网上有可以拿来用,需要修改定制)。比如简单爬虫是可以模板化的,做好一个后再做其他的就容易了。
我刚开始在github分享脚本,最近放上去了一个肺炎疫情的,搜索chenxuzhen应该能找到。
刷完这几本书再加十个以上小项目,我做简单的数据分析应该就畅通无阻了。我比较慢,中间放弃了很久(买房子耽误了很多时间),但是我估计我花了至少三个月,刷完这三本书四个月是必须的,因为中间还会尝试做简单的编程项目。
入门一般都是从简单爬虫入手,但是这个东西不需要掌握太多高深的,因为高难度的爬虫一个人很难做到,涉及到各种反爬策略(大网站都有),比如验证码复杂的不仅需要图像处理还需要机器学习(只是这两个技能入门也得一两个月了)。
我的目的就是数据分析,以后熟练了再好好看看数学尝试图像处理(这个很多和地球物理重叠,毕竟涉及到信号处理)。python可做的太多,我需要从工作需求开始,学习更快。
以上就是我简单的学习经验,看起来不多,但是都是刷了几千个帖子+实际操练得来的,纸上得来终觉浅,绝知此事要躬行,编程还是要靠实践。
这次上船后我有个小项目计划,就是从大量水下机器人抓取的图片中识别并抓取一些关键信息来校对图片的文件名并修改,因为这些都是rov technician手动录入错误百出,现在每天能步一百多节点,布一个节点就有布前,布中,收回前,三个图片,一个项目轻松数以万计的图片了,这些放入pdf中时都是需要检查一下的。pdf自动化同事已经做好了,bash+latex,我有时间用python重写一次。

扫我获取html仪表盘
最近做的BI Panel
刷码场景1
刷码场景2

pyecharts如何添加自定义图片

今天为了往pyecharts产生的html里添加微信二维码费了九牛二虎之力,主要是因为pyecharts的文档非常烂!

微信二维码
wechat QR
QR = myqr.run(
     words="https://geoseis.cn/CoV_en.html",          # 可以是字符串,也可以是网址(前面要加http(s)://)
     version=1,                              # 设置容错率为最高
     level='H',                              # 控制纠错水平,范围是L、M、Q、H,从左到右依次升高
     picture="C:/Users/xuzhen/Pictures/0.jpg",                           # 将二维码和图片合成
     colorized=True,                         # 彩色二维码
     contrast=1.0,                           #用以调节图片的对比度,1.0 表示原始图片,更小的值表示更低对比度,更大反之。默认为1.0
     brightness=1.0,                         #用来调节图片的亮度,其余用法和取值同上
     save_name="C:/Users/xuzhen/Pictures/python.gif",                     # 保存文件的名字,格式可以是jpg,png,bmp,gif
     save_dir=os.getcwd()                    #控制位置
 )
def image_base() -> Image:
     image = Image()
img_src = (
    "https://www.geoseis.cn/wp-content/gallery/pycharm/"
    "python.gif"
)
image.add(
    src=img_src,
    style_opts={"width": "200px", "height": "200px", "style": "margin-top: 10px"},
).set_global_opts(
    title_opts=ComponentTitleOpts(title="WeChat QR",title_style={"style": "font-size: 18px; font-weight:bold; color:red"},\
                                  subtitle="Scan to follow the realtime update!",subtitle_style={"style": "font-size: 12px; color:red"})
)
return image
  • 添加到page函数(重点是add(image_base(),如果没有这个括号是不行的,pyecharts没有这种实例需要自己琢磨它的用法):
page = (Page(page_title="2019-nCov")
        .add(world_map)
        .add(total_pie)
        .add(area_map)
        .add(area_heat_geo)
        .add(bar)
        .add(big_title)
        .add(times)
        .add(confirms)
        .add(confirms_people)
        .add(suspects)
        .add(suspects_people)
        .add(deads)
        .add(deads_people)
        .add(heals)
        .add(heals_people)
        .add(confirm_liquid)
        .add(suspect_liquid)
        .add(dead_liquid)
        .add(heal_liquid)
        .add(wc)
        .add(image_base())
        ).render("2019-nCov-RealTime.html")
  • 划重点,如何改变图片标题的字体(文档没有说明,需要阅读源码自己琢磨下),源码就在Options下的Charts_options.py:
title_opts=ComponentTitleOpts(title="WeChat QR",title_style={"style": "font-size: 18px; font-weight:bold; color:red"},\
subtitle="Scan to follow the realtime update!",subtitle_style={"style": "font-size: 12px; color:red"})
)

C:\Users\xuzhen\env\pyecharts\Lib\site-packages\pyecharts\options

最后的效果图:

肺炎疫情BI仪表盘