注:本文案例仅供技术学习,不代表研究性观点。

本文对应代码、数据及文献资料已上传至Github仓库https://github.com/CNFeffery/DataScienceStudyNotes

原始数据来自

https://github.com/BlankerL/DXY-COVID-19-Data

1 简介

拐点检测(Knee point detection),指的是在具有上升或下降趋势的曲线中,在某一点之后整体趋势明显发生变化,这样的点就称为拐点(如图1所示,在蓝色标记出的点之后曲线陡然上升):

图1

本文就将针对Python中用于拐点检测的第三方包kneed进行介绍,并以新型冠状肺炎数据为例,找出各指标数学意义上的拐点。

2 基于kneed的拐点检测

2.1 kneed基础

许多算法都需要利用肘部法则来确定某些关键参数,如K-means中聚类个数kDBSCAN中的搜索半径eps等。

在面对需要确定所谓肘部,即拐点时,人为通过观察来确定位置的方式不严谨,需要一套有数学原理支撑的检测方法。

Jeannie Albrecht等人在Finding a “Kneedle” in a Haystack: Detecting Knee Points in System Behavior(你可以在文章开头的Github仓库中找到)中从曲率的思想出发,针对离散型数据,结合离线、在线的不同应用场景以及Angle-basedMenger CurvatureEWMA等算法,提出了一套拐点检测方法。

kneed就是对这篇论文所提出算法的实现。

使用pip install kneed完成安装之后,下面我们来了解其主要用法:

2.1.1 KneeLocator

KneeLocatorkneed中用于检测拐点的模块,其主要参数如下:

x:待检测数据对应的横轴数据序列,如时间点、日期等
y:待检测数据序列,在x条件下对应的值,如x为星期一,对应的y为降水量
S:float型,默认为1,敏感度参数,越小对应拐点被检测出得越快
curve:str型,指明曲线之上区域是凸集还是凹集,concave代表凹,convex代表凸
direction:str型,指明曲线初始趋势是增还是减,increasing表示增,decreasing表示减
online:bool型,用于设置在线/离线识别模式,True表示在线,False表示离线;在线模式下会沿着x轴从右向左识别出每一个局部拐点,并在其中选择最优的拐点;离线模式下会返回从右向左检测到的第一个局部拐点

KneeLocator在传入参数实例化完成计算后,可返回的我们主要关注的属性如下:

kneeelbow:返回检测到的最优拐点对应的x

knee_yelbow_y:返回检测到的最优拐点对应的y

all_elbowsall_knees:返回检测到的所有局部拐点对应的x

all_elbows_yall_knees_y:返回检测到的所有局部拐点对应的y

curvedirection参数非常重要,用它们组合出想要识别出的拐点模式。

以余弦函数为例,在oonline设置为True时,分别在curve='concave'+direction='increasing'curve='concave'+direction='decreasing'curve='convex'+direction='increasing'curve='convex'+direction='decreasing'参数组合下对同一段余弦曲线进行拐点计算:

import matplotlib.pyplot as plt
from matplotlib import style
import numpy as np
from kneed import KneeLocatorstyle.use('seaborn-whitegrid')x = np.arange(1, 3, 0.01)*np.pi
y = np.cos(x)# 计算各种参数组合下的拐点
kneedle_cov_inc = KneeLocator(x,y,curve='convex',direction='increasing',online=True)kneedle_cov_dec = KneeLocator(x,y,curve='convex',direction='decreasing',online=True)kneedle_con_inc = KneeLocator(x,y,curve='concave',direction='increasing',online=True)kneedle_con_dec = KneeLocator(x,y,curve='concave',direction='decreasing',online=True)fig, axe = plt.subplots(2, 2, figsize=[12, 12])axe[0, 0].plot(x, y, 'k--')
axe[0, 0].annotate(s='Knee Point', xy=(kneedle_cov_inc.knee+0.2, kneedle_cov_inc.knee_y), fontsize=10)
axe[0, 0].scatter(x=kneedle_cov_inc.knee, y=kneedle_cov_inc.knee_y, c='b', s=200, marker='^', alpha=1)
axe[0, 0].set_title('convex+increasing')
axe[0, 0].fill_between(np.arange(1, 1.5, 0.01)*np.pi, np.cos(np.arange(1, 1.5, 0.01)*np.pi), 1, alpha=0.5, color='red')
axe[0, 0].set_ylim(-1, 1)axe[0, 1].plot(x, y, 'k--')
axe[0, 1].annotate(s='Knee Point', xy=(kneedle_cov_dec.knee+0.2, kneedle_cov_dec.knee_y), fontsize=10)
axe[0, 1].scatter(x=kneedle_cov_dec.knee, y=kneedle_cov_dec.knee_y, c='b', s=200, marker='^', alpha=1)
axe[0, 1].fill_between(np.arange(2.5, 3, 0.01)*np.pi, np.cos(np.arange(2.5, 3, 0.01)*np.pi), 1, alpha=0.5, color='red')
axe[0, 1].set_title('convex+decreasing')
axe[0, 1].set_ylim(-1, 1)axe[1, 0].plot(x, y, 'k--')
axe[1, 0].annotate(s='Knee Point', xy=(kneedle_con_inc.knee+0.2, kneedle_con_inc.knee_y), fontsize=10)
axe[1, 0].scatter(x=kneedle_con_inc.knee, y=kneedle_con_inc.knee_y, c='b', s=200, marker='^', alpha=1)
axe[1, 0].fill_between(np.arange(1.5, 2, 0.01)*np.pi, np.cos(np.arange(1.5, 2, 0.01)*np.pi), 1, alpha=0.5, color='red')
axe[1, 0].set_title('concave+increasing')
axe[1, 0].set_ylim(-1, 1)axe[1, 1].plot(x, y, 'k--')
axe[1, 1].annotate(s='Knee Point', xy=(kneedle_con_dec.knee+0.2, kneedle_con_dec.knee_y), fontsize=10)
axe[1, 1].scatter(x=kneedle_con_dec.knee, y=kneedle_con_dec.knee_y, c='b', s=200, marker='^', alpha=1)
axe[1, 1].fill_between(np.arange(2, 2.5, 0.01)*np.pi, np.cos(np.arange(2, 2.5, 0.01)*np.pi), 1, alpha=0.5, color='red')
axe[1, 1].set_title('concave+decreasing')
axe[1, 1].set_ylim(-1, 1)# 导出图像
plt.savefig('图2.png', dpi=300)

图中红色区域分别对应符合参数条件的搜索区域,蓝色三角形为每种参数组合下由kneed检测到的最优拐点:

图2

下面我们扩大余弦函数中x的范围,绘制出提取到的所有局部拐点:

x = np.arange(0, 6, 0.01)*np.pi
y = np.cos(x)# 计算convex+increasing参数组合下的拐点
kneedle = KneeLocator(x,y,curve='convex',direction='increasing',online=True)fig, axe = plt.subplots(figsize=[8, 4])axe.plot(x, y, 'k--')
axe.annotate(s='Knee Point', xy=(kneedle.knee+0.2, kneedle.knee_y), fontsize=10)
axe.set_title('convex+increasing')
axe.fill_between(np.arange(1, 1.5, 0.01)*np.pi, np.cos(np.arange(1, 1.5, 0.01)*np.pi), 1, alpha=0.5, color='red')
axe.fill_between(np.arange(3, 3.5, 0.01)*np.pi, np.cos(np.arange(3, 3.5, 0.01)*np.pi), 1, alpha=0.5, color='red')
axe.fill_between(np.arange(5, 5.5, 0.01)*np.pi, np.cos(np.arange(5, 5.5, 0.01)*np.pi), 1, alpha=0.5, color='red')
axe.scatter(x=list(kneedle.all_knees), y=np.cos(list(kneedle.all_knees)), c='b', s=200, marker='^', alpha=1)
axe.set_ylim(-1, 1)# 导出图像
plt.savefig('图3.png', dpi=300)

得到的结果如图3所示,其中注意,在使用kneed检测拐点时,落在最左或最右的拐点是无效拐点:

图3

2.2 探索新冠肺炎疫情数据

接下来我们尝试将上文介绍的kneed应用到新冠肺炎数据上,来探究各个指标数学意义上的拐点是否已经出现。

使用到的原始数据来自https://github.com/BlankerL/DXY-COVID-19-Data ,这个Github仓库以丁香园数据为数据源,实时同步更新粒度到城市级别的疫情发展数据。

你可以在本文开头提到的我的Github仓库对应本文路径下找到下文使用到的数据,更新时间为2020-02-18 22:55:07,下面开始我们的分析。

首先我们读入DXYArea.csv文件,并查看其信息,为了后面方便处理我们在读入时将updateTime列提前解析为时间格式:

import pandas as pdraw = pd.read_csv('DXYArea.csv', parse_dates=['updateTime'])
raw.info()

图4

查看其第一行信息:  

图5

可以看到,原始数据中包含了省、市信息,以及对应省及市的最新累计确诊人数累计疑似人数累计治愈人数累计死亡人数信息。

我们的目的是检测全国范围内,累计确诊人数日新增确诊人数治愈率死亡率随时间(单位:天)变化下的曲线,是否已经出现数学意义上的拐点(由于武汉市数据变化的复杂性和特殊性,下面的分析只围绕除武汉市之外的其他地区进行)。

首先我们对所有市取每天最晚一次更新的数据作为当天正式的记录值:

# 抽取updateTime列中的年、月、日信息分别保存到新列中
raw['year'], raw['month'], raw['day'] = list(zip(*raw['updateTime'].apply(lambda d: (d.year, d.month, d.day))))# 得到每天每个市最晚一次更新的疫情数据
temp = raw.sort_values(['provinceName', 'cityName', 'year', 'month', 'day', 'updateTime'],ascending=False,ignore_index=True).groupby(['provinceName', 'cityName', 'year', 'month', 'day']) \.agg({'province_confirmedCount': 'first','province_curedCount': 'first','province_deadCount': 'first','city_confirmedCount': 'first','city_curedCount': 'first','city_deadCount': 'first'}) \.reset_index(drop=False)# 查看前5行
temp.head()

图6

有了上面处理好的数据,接下来我们针对全国(除武汉市外)的相关指标的拐点进行分析。

首先我们来对截止到今天(2020-2-18)我们关心的指标进行计算并做一个基本的可视化:

# 计算各指标时序结果
# 全国(除武汉市外)累计确诊人数
nationwide_confirmed_count = temp[temp['cityName'] != '武汉'].groupby(['year', 'month', 'day']) \.agg({'city_confirmedCount': 'sum'}) \.reset_index(drop=False)# 全国(除武汉市外)累计治愈人数
nationwide_cured_count = temp[temp['cityName'] != '武汉'].groupby(['year', 'month', 'day']) \.agg({'city_curedCount': 'sum'}) \.reset_index(drop=False)# 全国(除武汉市外)累计死亡人数
nationwide_dead_count = temp[temp['cityName'] != '武汉'].groupby(['year', 'month', 'day']) \.agg({'city_deadCount': 'sum'}) \.reset_index(drop=False)# 全国(除武汉市外)每日新增确诊人数,即为nationwide_confirmed_count的一阶差分
nationwide_confirmed_inc_count = nationwide_confirmed_count['city_confirmedCount'].diff()[1:]# 全国(除武汉市外)治愈率
nationwide_cured_ratio = nationwide_cured_count['city_curedCount'] / nationwide_confirmed_count['city_confirmedCount']# 全国(除武汉市外)死亡率
nationwide_died_ratio = nationwide_dead_count['city_deadCount'] / nationwide_confirmed_count['city_confirmedCount']# 绘图#解决中文显示问题
plt.rcParams['font.sans-serif'] = ['KaiTi']
plt.rcParams['axes.unicode_minus'] = Falsefig, axes = plt.subplots(3, 2, figsize=[12, 18])axes[0, 0].plot(nationwide_confirmed_count.index, nationwide_confirmed_count['city_confirmedCount'], 'k--')
axes[0, 0].set_title('累计确诊人数', fontsize=20)
axes[0, 0].set_xticks(nationwide_confirmed_count.index)
axes[0, 0].set_xticklabels([f"{nationwide_confirmed_count.loc[i, 'month']}-{nationwide_confirmed_count.loc[i, 'day']}"for i in nationwide_confirmed_count.index], rotation=60)axes[0, 1].plot(nationwide_cured_count.index, nationwide_cured_count['city_curedCount'], 'k--')
axes[0, 1].set_title('累计治愈人数', fontsize=20)
axes[0, 1].set_xticks(nationwide_cured_count.index)
axes[0, 1].set_xticklabels([f"{nationwide_cured_count.loc[i, 'month']}-{nationwide_cured_count.loc[i, 'day']}"for i in nationwide_cured_count.index], rotation=60)axes[1, 0].plot(nationwide_dead_count.index, nationwide_dead_count['city_deadCount'], 'k--')
axes[1, 0].set_title('累计死亡人数', fontsize=20)
axes[1, 0].set_xticks(nationwide_dead_count.index)
axes[1, 0].set_xticklabels([f"{nationwide_dead_count.loc[i, 'month']}-{nationwide_dead_count.loc[i, 'day']}"for i in nationwide_dead_count.index], rotation=60)axes[1, 1].plot(nationwide_confirmed_inc_count.index, nationwide_confirmed_inc_count, 'k--')
axes[1, 1].set_title('每日新增确诊人数', fontsize=20)
axes[1, 1].set_xticks(nationwide_confirmed_inc_count.index)
axes[1, 1].set_xticklabels([f"{nationwide_confirmed_count.loc[i, 'month']}-{nationwide_confirmed_count.loc[i, 'day']}"for i in nationwide_confirmed_inc_count.index], rotation=60)axes[2, 0].plot(nationwide_cured_ratio.index, nationwide_cured_ratio, 'k--')
axes[2, 0].set_title('治愈率', fontsize=20)
axes[2, 0].set_xticks(nationwide_cured_ratio.index)
axes[2, 0].set_xticklabels([f"{nationwide_cured_count.loc[i, 'month']}-{nationwide_cured_count.loc[i, 'day']}"for i in nationwide_cured_ratio.index], rotation=60)axes[2, 1].plot(nationwide_died_ratio.index, nationwide_died_ratio, 'k--')
axes[2, 1].set_title('死亡率', fontsize=20)
axes[2, 1].set_xticks(nationwide_died_ratio.index)
axes[2, 1].set_xticklabels([f"{nationwide_dead_count.loc[i, 'month']}-{nationwide_dead_count.loc[i, 'day']}"for i in nationwide_died_ratio.index], rotation=60)fig.suptitle('全国范围(除武汉外)', fontsize=30)# 导出图像
plt.savefig('图7.png', dpi=300)

图7

接着就到了检测拐点的时候了。

为了简化代码,我们先编写自定义函数,用于从KneeLocatorcurvedirection参数的全部组合中,搜索合法的拐点输出值及对应拐点的趋势变化类型,若无则返回None:

def knee_point_search(x, y):# 转为list以支持负号索引x, y = x.tolist(), y.tolist()output_knees = []for curve in ['convex', 'concave']:for direction in ['increasing', 'decreasing']:model = KneeLocator(x=x, y=y, curve=curve, direction=direction, online=False)if model.knee != x[0] and model.knee != x[-1]:output_knees.append((model.knee, model.knee_y, curve, direction))if output_knees.__len__() != 0:print('发现拐点!')return output_kneeselse:print('未发现拐点!')

下面我们对每个指标进行拐点搜索。

先来看看累计确诊数,经过程序的搜索,并未发现有效拐点:

图8

接着检测累计治愈数,发现了有效拐点:

图9

在曲线图上标记出拐点:

knee_info = knee_point_search(x=nationwide_cured_count.index,y=nationwide_cured_count['city_curedCount'])
fig, axe = plt.subplots(figsize=[8, 6])
axe.plot(nationwide_cured_count.index, nationwide_cured_count['city_curedCount'], 'k--')
axe.set_title('累计治愈人数', fontsize=20)
axe.set_xticks(nationwide_cured_count.index)
axe.set_xticklabels([f"{nationwide_cured_count.loc[i, 'month']}-{nationwide_cured_count.loc[i, 'day']}"for i in nationwide_cured_count.index], rotation=60)for point in knee_info:axe.scatter(x=point[0], y=point[1], c='b', s=200, marker='^')axe.annotate(s=f'{point[2]} {point[3]}', xy=(point[0]+1, point[1]), fontsize=14)# 导出图像
plt.savefig('图10.png', dpi=300)

图10

结合其convex+increasing的特点,可以说明从2月5日开始,累计治愈人数有了明显的加速上升趋势。

再来看看累计死亡人数

图11

绘制出其拐点:

图12

同样在2月5日开始,累计死亡人数跟累计治愈人数同步,有了较为明显的加速上升趋势。

对于日新增确诊数则找到了两个拐点,虽然这个指标在变化趋势上看波动较为明显,但结合其参数信息还是可以推断出其在第一个拐点处增速放缓,在第二个拐点出加速下降,说明全国除武汉之外的地区抗疫工作已经有了明显的成果:

图13

治愈率死亡率同样出现了拐点,其中治愈率出现加速上升的拐点,伴随着广大医疗工作者的辛勤付出,更好的疗法加速了治愈率的上升:

图14

死亡率虽然最新一次的拐点代表着加速上升,但通过比较其与治愈率的变化幅度比较可以看出,死亡率的绝对增长量十分微弱:

图15

通过上面的分析,可以看出在这场针对新冠肺炎的特殊战役中,到目前为止,除武汉外其他地区已取得阶段性的进步,但仍然需要付出更大的努力来巩固来之不易的改变。

相信只要大家都能从自己做起,不给病毒留可趁之机,更加明显的胜利拐点一定会出现。

-END-

往期精彩回顾适合初学者入门人工智能的路线及资料下载机器学习在线手册深度学习在线手册AI基础下载(pdf更新到25集)备注:加入本站微信群或者qq群,请回复“加群”获取一折本站知识星球优惠券,请回复“知识星球”喜欢文章,点个在看

含最新数据! 使用Python检测新冠肺炎疫情拐点相关推荐

  1. python 绘制新冠肺炎疫情地图

    参考链接: (1)实时更新|新冠肺炎疫情地图 https://news.sina.cn/zt_d/yiqing0121 (2)实时的可视化疫情地图 https://blog.csdn.net/weix ...

  2. 数据分享——EPS数据库-新冠肺炎疫情实时监控平台

    原文链接:https://www.lianxh.cn/news/af018044fad9e.html 与 EPS 数据库沟通后,与大家一起分享最新的舆情监控数据. 疫情数据,牵动人心!数据是我们在疫情 ...

  3. 最新!兰州大学发布对上海市的新冠肺炎疫情预测!

    这段时间,上海市疫情牵动着所有人的心.据数据显示,自 2022 年 3 月 1 日上海市报告新冠肺炎本土确诊病例和本土无症状感染者以来,截至 2022 年 4 月 10 日 24 时,上海市已累计报告 ...

  4. 每日一练:Python爬虫爬取全国新冠肺炎疫情数据实例详解,使用beautifulsoup4库实现

    Python 爬虫篇 - 爬取全国新冠肺炎疫情数据实例详解 效果图展示 第一章:疫情信息的下载与数据提取 ① 爬取页面数据到本地 ② json 字符串正则表达式分析 ③ 提取数据中的 json 字符串 ...

  5. 爬取并处理中国新冠肺炎疫情数据

    项目名称: 爬取并处理中国新冠肺炎疫情数据 目的: 通过Python爬取中国新冠肺炎疫情数据,存入Excel,对此数据分析并进行可视化,制作查询中国疫情情况的GUI界面. 具体内容: 通过Python ...

  6. 新冠肺炎疫情数据爬取以及几种简单的地图可视化方法

    众所周知,新冠肺炎疫情是一次很流行的全球性公共卫生事件.如今我国疫情已经好了许多,但世界各国的疫情依然严峻.特殊时期,正好尝试一下疫情网络数据的抓取,并用几种python库对数据进行简单的地图可视化( ...

  7. 新冠肺炎疫情数据可视化分析-FineBI

    目录 一.实验(实训)目的 二.实验(实训)原理或方法 三.仪器设备.材料 四.实验(实训)步骤 五.实训记录及结果 <------------------------------------- ...

  8. 【大数据平台】基于Spark的美国新冠肺炎疫情数据分析及预测

    (本实验系中国地质大学(武汉)2022年秋期大数据平台及应用课程设计) 一.选题背景 新型冠状病毒疫情是由严重急性呼吸系统综合征冠状病毒2(SARS-CoV-2)导致的2019冠状病毒病(COVID- ...

  9. 【Python】2020年美国新冠肺炎疫情数据分析

    2020年美国新冠肺炎疫情数据分析 一. 需求描述 二. 环境介绍 三. 数据来源描述 四. 数据上传及上传结果查看 五.数据处理过程描述 1.数据集下载 2.格式转换 3.启动Hadoop集群 4. ...

最新文章

  1. 【Codeforces】Round #375 (Div. 2)
  2. 计算体系结构的演进规律
  3. 《大话设计模式》第29章-OOTV杯超级模式大赛—模式总结(四)
  4. ASP错误捕获的几种常规处理方式
  5. stm32 GPIO简单介绍及初始化配置(库函数)
  6. code1928: 日期差值 技巧模拟
  7. 多项新政催生本年度购房最佳“窗口期”
  8. 关于Python对齐问题
  9. 【转载】Linux平台软件包管理完全攻略
  10. 凉山州计算机等级考试时间,2020年四川凉山中考考试时间及科目安排(已公布)...
  11. SQL 数据对比(case when in)
  12. HP RDX备份磁带系统的突破性特点
  13. w10安装ubuntu_记 Win10 + Ubuntu 双系统安装
  14. SSD 输入图片尺寸、比例
  15. php网易云信im即时通讯和聊天室
  16. Android 根据网络图片URL转Bitmap对象
  17. 动态规划之二项式系数
  18. Redisson(2-1)分布式锁实现对比 VS Java的ReentrantLock之tryLock
  19. 荣耀MagicOS 7.0正式发布 打造以人为中心的智慧生活解决方案
  20. 数字MIC(es7202 PDM协议)MIC录音声音较小

热门文章

  1. 协程:Greenlet模块、Gevent模块
  2. 微信小程序开发02-小程序基本介绍
  3. java 脚本引擎执行javascript脚本
  4. Java VisualVM插件地址
  5. 怎么在VS监视DataSet类型的数据
  6. linux学习笔记之--vim 程序编辑器
  7. js生日计算年龄_生男生女计算公式超准
  8. Python-OpenCV学习 -- 台式机外接USB摄像头的视频读取
  9. 北斗导航 | 卫星导航基础知识(卫星轨道及卫星在轨运动)
  10. 北斗导航 | 利用模拟卫星星座估计GNSS接收机位置