使用python爬取并分析疫情数据
目录
数据获取 1
数据分析和展示 1
数据读入和清洗 1
累计确诊数排名前 20 的国家名称及其数量 1
15 天中,每日新增确诊数累计排名前 10 个国家的每日新增确诊数据的曲线图 1
累计确诊人数占国家总人口比例最高的 10 个国家 1
死亡率(累计死亡人数/累计确诊人数)最低的 10 个国家 1
用饼图展示各个国家的累计确诊人数的比例 1
展示全球各个国家累计确诊人数的箱型图,有平均值 1
另外一些数据 1
全世界应对新冠疫情最好的10个国家 1
预测分析 1
预测方法 1
预测程序 1
结果以及分析 1
代码网址 1
数据获取
选取wikipedia上的疫情数据,总数据页如下
每个国家的数据都可以从表格中解析出来,例如United States的详细数据的地址为 Unitedd States。 具体数据可以从下图中的表格解析:
仔细分析之后我们可以写以下爬虫代码:
1import scrapy
2from ..items import FordataItem
3
4class mySpider(scrapy.spiders.Spider):
5 name = "forData"
6
7 def start_requests(self):
8 beginUrl= "https://en.wikipedia.org/w/index.php?title=Template:COVID-19_pandemic_data"
9 myHeader = {
10 'Content-Type': 'application/json',
11 'User-Agent':'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0'
12 }
13 #先爬出数据首页,找到有哪些国家
14 yield scrapy.Request(url=beginUrl,callback=self.parseCountryUrl,headers=myHeader)
1 def parseCountryUrl(self,response):
2 cnt=0
3 for each in response.xpath('/html/body/div[3]/div[3]/div[5]/div[1]/div[4]/div[2]/table/tbody/tr'):
4 countryName=each.xpath('th[2]/a/text()').get()
5 countryUrl=each.xpath('th[2]/a/@href').extract()
6 if(countryName!=None and len(countryUrl)>0):
7 cnt=cnt+1
8 print(countryName,countryUrl)
9 countryDataUrl= "https://en.wikipedia.org"+countryUrl[0]
10 myHeader = {
11 'Content-Type': 'application/json',
12 'User-Agent':'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'
13 }
14 yield scrapy.Request(url=countryDataUrl,headers=myHeader,callback=lambda response,name=countryName:self.parseCountryCase(response,name))
15 #再分别爬取每个国家的数据
16 print(cnt)
17
18 def parseCountryCase(self,response,name):
19 ct=0
20 for tr in response.css('tr.mw-collapsible'):
21 item=FordataItem()
22 item['country']=name
23 item['date']=tr.xpath('td[1]/text()').get()
24 item['cases']=tr.xpath('td[3]/span[1]/text()').get()
25 item['deaths']=tr.xpath('td[4]/span[1]/text()').get()
26 #解析出每个国家的名字、日期、病例、死亡数
27 if('2020' in item['date']):
28 ct=ct+1
29 yield(item)
30 if(ct==0):
31 print("没有单日数据的的国家:",name)
数据前几行如下:
另外,这个网页上并没有人口总数的数据可以从https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_population上继续爬取。
可以写出如下爬虫
1import scrapy
2from ..items import ForpopulationItem
3
4class mySpider(scrapy.spiders.Spider):
5 name = "forPopulation"
6
7 def start_requests(self):
8 beginUrl= "https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_population"
9 myHeader = {
10 'Content-Type': 'application/json',
11 'User-Agent':'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0'
12 }
13 #网页请求
14 yield scrapy.Request(url=beginUrl,callback=self.parsePopulation,headers=myHeader)
15
16 def parsePopulation(self,response):
17 for tr in response.xpath('/html/body/div[3]/div[3]/div[5]/div[1]/table/tbody/tr'):
18 #数据解析
19 item=ForpopulationItem()
20 item['country']=tr.xpath('td[1]/a/text()').get()
21 item['population']=tr.xpath('td[2]/text()').get()
22 if(item['country'] and item['population']):
23 item['population'].replace(',','')
24 print(item)
25 yield item
爬取结果如下
数据分析和展示
数据读入和清洗
从前面爬取的数据文件1myData.csv
和1population.csv
中读取数据。 对于疫情数据,将空值填充为0,只保留日起在12020-11-30
到12020-12-15
之间的数据。 将病例数1cases
和1deaths
格式转换为1int64
。 根据1country
和1date
进行去重。
1import pandas
2import numpy
3import matplotlib
4import matplotlib.pyplot as plt
5from brokenaxes import brokenaxes
6import seaborn
7
8org=pandas.read_csv('myData.csv')
9org=org.fillna('0')
10org['cases']=org['cases'].str.replace('\D+','').astype(int)
11org['deaths']=org['deaths'].str.replace('\D+','').astype(int)
12org=org[org['date']<'2020-12-16']
13org=org[org['date']>'2020-11-29']
14org.drop_duplicates(subset = ['country','date'],keep='first',inplace=True)
15myData=org
16population=pandas.read_csv('population.csv')
17population['population']=population['population'].str.replace('\D+','').astype(int)
18
19print("收集到的",len(myData['country'].value_counts(dropna=False)),"个国家的数据")
收集到的 135 个国家的数据
1## 15天中,全球新冠疫情的总体变化趋势
2
3
4####################################
5#a) 15 天中,全球新冠疫情的总体变化趋势#
6####################################
7
8print("\n\n====15 天中,全球新冠疫情的总体变化趋势======")
9
10#这十六天内的全球累计病例
11totalCase=[0]*16
12
13#这16天内的全球死亡累计病例
14totalDeaths=[0]*16
15
16#December表示日期,用字符串存储,方便图例生成
17December=['']*16
18
19#根据国家进行分组,全球数量=个国家数量之和,计算累计病例和死亡病例
20casesSumByDay=myData.pivot_table(index='date',values=['cases'],aggfunc=sum)
21deathsSumByDay=myData.pivot_table(index='date',values=['deaths'],aggfunc=sum)
22
23December[0]='2020-11-30'
24totalCase[0]=casesSumByDay.loc['2020-11-30']['cases']
25totalDeaths[0]=deathsSumByDay.loc['2020-11-30']['deaths']
26
27for i in range(1,16):
28December[i]='2020-12-'+('0' if i<10 else '')+str(i)
29totalCase[i]=casesSumByDay.loc[December[i]]['cases']
30totalDeaths[i]=deathsSumByDay.loc[December[i]]['deaths']
31
32print("11-30到12-15感染病例:",totalCase)
33print("11-30到12-15死亡病例:",totalDeaths)
34
35# matplotlib绘图显示中文
36
37plt.rcParams["font.family"]="SimHei"
38
39#画图部分
40mycolors = ['tab:red', 'tab:blue']
41fig, ax = plt.subplots(1,1,figsize=(16, 9), dpi= 300)
42x = December
43y = numpy.vstack([totalDeaths,totalCase])
44labs=['累积死亡数','累积感染病例']
45ax = plt.gca()
46ax.stackplot(x, y, labels=labs, colors=mycolors, alpha=0.8)
47plt.title("15 天中,全球新冠疫情的总体变化趋势")
48ax.legend(fontsize=10, ncol=4)
49plt.xticks(rotation=30,fontsize=10) # 这里是调节横坐标的倾斜度
50plt.gca().spines["top"].set_alpha(0)
51plt.gca().spines["bottom"].set_alpha(.3)
52plt.gca().spines["right"].set_alpha(0)
53plt.gca().spines["left"].set_alpha(.3)
54plt.savefig('a.png')
1====15 天中,全球新冠疫情的总体变化趋势======
211-30到12-15感染病例: [58806605, 59867806, 59924956, 60557860, 61163129, 59527002, 60033395, 62585050, 62008006, 63602677, 65008985, 65672468, 63968990, 64505434, 67261669, 68293227]
311-30到12-15死亡病例: [1471087, 1492594, 1494629, 1506452, 1517510, 1475608, 1482627, 1541590, 1516612, 1564328, 1576069, 1588076, 1541701, 1549901, 1611817, 1633944]
累计确诊数排名前 20 的国家名称及其数量
1####################################################
2#b) 累计确诊数排名前 20 的国家名称及其数量; #
3####################################################
4print("\n\n=======累计确诊数排名前 20 的国家名称及其数量=========")
5
6#总的累计数量只需要看最后一天的数量
7casesMaxByCountry=myData.pivot_table(index='country',values=['cases'],aggfunc=max)
8casesMaxByCountry=casesMaxByCountry.sort_values('cases', ascending=False)
9
10print(casesMaxByCountry.head(20))
输出结果:
1=======累计确诊数排名前 20 的国家名称及其数量=========
2cases
3country
4United States 16022297
5India 9906165
6Brazil 6970034
7Russia 2707945
8France 2391447
9Turkey 1898447
10United Kingdom 1888116
11Italy 1870576
12Spain 1762212
13Argentina 1510186
14Colombia 1444646
15Germany 1351510
16Mexico 1267202
17Poland 1147446
18Iran 1123474
19Peru 987675
20Ukraine 919704
21South Africa 873679
22Indonesia 629429
23Netherlands 628577
15 天中,每日新增确诊数累计排名前 10 个国家的每日新增确诊数据的曲线图
1##########################################################################
2#c)15 天中,每日新增确诊数累计排名前 10 个国家的每日新增确诊数据的曲线图; #
3##########################################################################
4mycolors=['tab:blue','tab:orange','tab:green','tab:red','tab:purple','tab:brown','tab:pink','tab:gray','tab:olive','tab:cyan']
5plt.clf()
6
7#每个国家的12-1之前的累计数量
8casesMinByCountry=myData.pivot_table(index='country',values=['cases'],aggfunc=min)
9
10#每个国家12-1到12-15的新增数量
11decCaseMaxByCountry=(casesMaxByCountry-casesMinByCountry).sort_values('cases', ascending=False)
12
13cnt=0
14countryCName=[]
15figC=[0]*10
16for index,row in decCaseMaxByCountry.iterrows():
17cnt=cnt+1
18countryCName.append(index)
19yi=myData[myData['country']==index]['cases'].tolist()
20for j in range(15,0,-1):#得到每日变化数据
21yi[j]=yi[j]-yi[j-1]
22figC[cnt-1],=plt.plot(December[1:],yi[1:],color=mycolors[cnt-1],linewidth=2.0,linestyle='-.')
23if(cnt>=10):#只保留前十个国家
24break
25
26plt.title("每日新增确诊排名前 10 的国家的数据曲线图")
27plt.legend(handles=figC,labels=countryCName)
28plt.savefig('c.png')
累计确诊人数占国家总人口比例最高的 10 个国家
1################################################
2#d) 累计确诊人数占国家总人口比例最高的 10 个国家 #
3################################################
4print("\n\n=====累计确诊人数占国家总人口比例最高的 10 个国家======")
5
6#这会用到populaton.csv的数据
7#累计确诊比例
8casesRatio=[]
9for index,row in casesMaxByCountry.iterrows():
10tmp=population[population['country']==index]['population']
11if(len(tmp)>0):#这里需要注意没有人口数据的国家
12casesRatio.append({'casesRatio':row['cases']/tmp.iloc[0],'name':index})
13
14#升序排序取最后10个
15casesRatio.sort(key=lambda x:x['casesRatio'])
16for i in casesRatio[-1:-10:-1]:
17print(i)
结果如下:
1=====累计确诊人数占国家总人口比例最高的 10 个国家======
2{'casesRatio': 0.09561146718594844, 'name': 'Andorra'}
3{'casesRatio': 0.06748037079864816, 'name': 'Luxembourg'}
4{'casesRatio': 0.05548972112860494, 'name': 'Czech Republic'}
5{'casesRatio': 0.05271911310260917, 'name': 'Belgium'}
6{'casesRatio': 0.05243676244828293, 'name': 'Georgia'}
7{'casesRatio': 0.05198869490976536, 'name': 'Qatar'}
8{'casesRatio': 0.04872524937150579, 'name': 'Moldova'}
9{'casesRatio': 0.04842282774074547, 'name': 'United States'}
10{'casesRatio': 0.046797668330376366, 'name': 'Slovenia'}
死亡率(累计死亡人数/累计确诊人数)最低的 10 个国家
1#########################################################
2#e) 死亡率(累计死亡人数/累计确诊人数)最低的 10 个国家; #
3#########################################################
4print("\n\n====死亡率(累计死亡人数/累计确诊人数)最低的 10 个国家=======")
5
6#每个国家死亡人数
7deathsMaxByCountry=myData.pivot_table(index='country',values=['deaths'],aggfunc=max)
8#每个国家的死亡率
9deathsRatio=deathsMaxByCountry.rename(columns={'deaths':'deathsRatio'})
10deathsRatio=deathsRatio/casesMaxByCountry.rename(columns={'cases':'deathsRatio'})
11print((deathsRatio.sort_values('deathsRatio')).head(10))
结果如下:
1====死亡率(累计死亡人数/累计确诊人数)最低的 10 个国家=======
2deathsRatio
3country
4Seychelles 0.000000
5Guyana 0.000000
6Saint Lucia 0.000000
7Faroe Islands 0.000000
8Saint Kitts and Nevis 0.000000
9Falkland Islands 0.000000
10Brunei 0.000000
11Dominica 0.000000
12Saint Vincent and The Grenadines 0.000000
13Singapore 0.000497
用饼图展示各个国家的累计确诊人数的比例
1
2########################################
3#f) 用饼图展示各个国家的累计确诊人数的比例#
4########################################
5print("\n\n=====各个国家的累计确诊人数的比例=======")
6
7#把占比最大的几个国家列出来,直到这些国家占比超过75%,其他国家表示为other
8cnt=0
9figData=[]
10for index,row in casesMaxByCountry.iterrows():
11cnt=cnt+row['cases']
12if cnt/totalCase[15]>0.75:
13break
14figData.append((index,row['cases']))
15figData.append(('others',(totalCase[15]-cnt)))
16
17#输出一下具体数据
18print(figData)
19
20#画图部分
21plt.clf()
22fig, ax = plt.subplots(figsize=(10, 5), ncols=2,dpi=300)
23ax1, ax2 = ax.ravel()
24patches, texts = ax1.pie(dict(figData).values(),
25shadow=True,startangle=90,
26labels=[ str(country)+','+str('%.2lf'%(tot*100/totalCase[15]))+'%' for country, tot in figData])
27ax1.set_title('各个国家的累计确诊人数的比例')
28ax2.axis('off')
29ax2.legend(patches, [i for i,j in figData], loc='center left')
30plt.tight_layout()
31plt.savefig('f.png')
1=====各个国家的累计确诊人数的比例=======
2[('United States', 16022297), ('India', 9906165), ('Brazil', 6970034), ('Russia', 2707945), ('France', 2391447), ('Turkey', 1898447), ('United Kingdom', 1888116), ('Italy', 1870576), ('Spain', 1762212), ('Argentina', 1510186), ('Colombia', 1444646), ('Germany', 1351510), ('Mexico', 1267202), ('others', 16154998)]
展示全球各个国家累计确诊人数的箱型图,有平均值
1################################################
2#g) 展示全球各个国家累计确诊人数的箱型图,要有平均值#
3################################################
4print("\n\n=====全球各个国家累计确诊人数的箱型图=====")
5
6plt.clf()
7fig, ax = plt.subplots(figsize=(5, 10),dpi=300)
8#图中的黄点是平均数据值
9seaborn.stripplot(y='cases',data={'cases':[totalCase[15]/135]}, color="orange", size=2.5)
10seaborn.boxplot(data=casesMaxByCountry,linewidth=0.3,fliersize=1)
11plt.title("全球各个国家累计确诊人数的箱型图")
12plt.savefig('g.png')
另外一些数据
115日新增确诊占累积确诊病例最小的20个国家
2
3########################################
4
5# 15日新增确诊占累积确诊病例最小的20个国家#
6
7########################################
8minRatio=(decCaseMaxByCountry/casesMaxByCountry).sort_values('cases')
9print(minRatio.head(20))
1
2cases
3country
4Comoros 0.000000
5Singapore 0.002108
6Australia 0.005099
7Malawi 0.008553
8Isle of Man 0.010724
9Suriname 0.012823
10Brunei 0.013158
11Guernsey 0.013746
12Sierra Leone 0.015912
13Qatar 0.017265
14Ivory Coast 0.017774
15New Zealand 0.019084
16Bolivia 0.020363
17Nepal 0.022645
18Benin 0.024272
19Peru 0.024370
20São Tomé and Príncipe 0.024752
21Maldives 0.026706
22Kuwait 0.027776
23Iceland 0.029580
全世界应对新冠疫情最好的10个国家
注意:爬取的数据没有包含中国的数据。
显而易见的,感染人数过多(大于一百万)的国家应对疫情肯定不是最好的。 感染人数较少(小于100)也不需要考虑,这可能疫情并没有爆发,只是几个境外输入病例。 我们只考虑疫情严重的几个国家。这之后按照人口递减排序,取前10个。
1
2#####################
3#最好的10个国家 #
4#####################
5print("\n\n======应对疫情最好的10个国家========")
6good=[]
7for index,rows in casesMaxByCountry.iterrows():
8tmp=population[population['country']==index]['population']
9if((100<rows['cases']<1000000) and len(tmp)>0):
10good.append({'population':tmp.iloc[0],'name':index})
11
12good.sort(key=lambda x:-x['population'])
13
14for i in range(10):
15print(good[i]['name'])
1======应对疫情最好的10个国家========
2Indonesia
3Pakistan
4Nigeria
5Japan
6Philippines
7Ethiopia
8Egypt
9Vietnam
10South Africa
11Myanmar
预测分析
预测方法
采用线性回归的方法进行预测。 新冠疫情存在一定的趋势,但目前数据比较少(只有10天),可以假设不存在季节性。 这样,可以采用1线性回归
的方法进行预测。
预测程序
1
2###################################
3
4# 预测分析 #
5
6###################################
7
8from sklearn import linear_model
9regr = linear_model.LinearRegression()
10
11# 拟合
12
13regr.fit(numpy.array([i for i in range(10)]).reshape(-1, 1), totalCase[1:11])
14
15# 得到直线的斜率、截距
16
17a, b = regr.coef_, regr.intercept_
18
19# 给出待预测日期
20
21predictDate = numpy.array([i for i in range(10,16)]).reshape(-1,1)
22
23#输出一下预测值
24print(regr.predict(predictDate))
25
26#画图部分
27plt.clf()
28fig, ax = plt.subplots(figsize=(16, 10),dpi=300)
29plt.scatter(December, totalCase, color='blue')
30plt.plot(December[1:], regr.predict(numpy.array([i for i in range(15)]).reshape(-1,1)), color='red', linewidth=4)
31plt.savefig('i.png')
1======预测分析========
2[64229138.06666667 64738456.51515152 65247774.96363637 65757093.41212121
366266411.86060607 66775730.30909091]
结果以及分析
最后五天的数据拟合程度较好。
对于短期的数据,线性回归应该是较好的一种预测方法。 然而,再实际问题中,还需要考虑各种影响因素。 比如,各个国家政策的调整,病毒的变异等等。