Matplotlib学习---用matplotlib画折线图(line chart)

时间:2023-03-08 17:36:57

这里利用Jake Vanderplas所著的《Python数据科学手册》一书中的数据,学习画图。

数据地址:https://raw.githubusercontent.com/jakevdp/data-CDCbirths/master/births.csv

准备工作:先导入matplotlib和pandas,用pandas读取csv文件,然后创建一个图像和一个坐标轴

import pandas as pd
from matplotlib import pyplot as plt
birth=pd.read_csv(r"https://raw.githubusercontent.com/jakevdp/data-CDCbirths/master/births.csv")
fig,ax=plt.subplots()

我们想要画一个反映每天平均出生人数的折线图,看看节假日是否对出生人数有影响。

折线图: ax.plot(x,y,marker="-",color="black")

这个数据文件比较大,在用print(birth.head())和print(birth.tail())分别查看前后5行数据后,发现day这一列的数据中有null字样。

用print(birth[birth["day"]!="null"])命令查看没有null字样的数据(此处只截取部分):

       year  month day gender  births
0 1969 1 1 F 4046
1 1969 1 1 M 4440
2 1969 1 2 F 4454
3 1969 1 2 M 4548
4 1969 1 3 F 4548
5 1969 1 3 M 4994
6 1969 1 4 F 4440
7 1969 1 4 M 4520
8 1969 1 5 F 4192
9 1969 1 5 M 4198
10 1969 1 6 F 4710
11 1969 1 6 M 4850
12 1969 1 7 F 4646
13 1969 1 7 M 5092
14 1969 1 8 F 4800
15 1969 1 8 M 4934
16 1969 1 9 F 4592
17 1969 1 9 M 4842

用print(birth[birth["day"]=="null"])命令查看有null字样的数据(此处只截取部分):

       year  month   day gender  births
15067 1989 1 null F 156749
15068 1989 1 null M 164052
15069 1989 2 null F 146710
15070 1989 2 null M 154047
15071 1989 3 null F 165889
15072 1989 3 null M 174433
15073 1989 4 null F 155689
15074 1989 4 null M 163432
15075 1989 5 null F 163800
15076 1989 5 null M 172892
15077 1989 6 null F 165525
15078 1989 6 null M 173823
15079 1989 7 null F 174054
15080 1989 7 null M 183063
15081 1989 8 null F 178986
15082 1989 8 null M 188074
15083 1989 9 null F 174808
15084 1989 9 null M 182962

可以看出,从1989年开始,数据画风突变,此前记录的是每天的数据,后面记录的是每个月的数据。

这里我们先把1969年-1988年的数据提取出来进行统计:birth=birth.iloc[:15067],然后用birth.info()命令查看各列数据的类型,发现day这一列数据类型是object,因此需要转换其数据类型:

birth["day"]=birth["day"].astype(int)

接下来把year,month和day这三列的数据结合起来,用pd.to_datetime()转化为时间序列格式。结果发生了错误:

ValueError: cannot assemble the datetimes: 'int' object is unsliceable

网上查找原因,在*上有人解答说是因为日期可能超出了允许的范围:

The valid day range is between 1 and 31. Check your data again make sure all the columns are within allowable range.

现在需要把超标的地方找出来,看一看究竟是什么错误。用print(birth[birth["day"]>31])命令查看day超过31的地方,结果发现有好多(此处只截取部分):

       year  month  day gender  births
62 1969 1 99 F 26
63 1969 1 99 M 38
126 1969 2 99 F 42
127 1969 2 99 M 48
190 1969 3 99 F 64
191 1969 3 99 M 50
254 1969 4 99 F 50
255 1969 4 99 M 66
318 1969 5 99 F 54
319 1969 5 99 M 52
382 1969 6 99 F 54
383 1969 6 99 M 48
446 1969 7 99 F 24
447 1969 7 99 M 44

不清楚是什么情况,在这里先把这些超标数据去除:birth=birth[birth["day"]<=31]。结果发现还是不对,把数据再调出来看了之后,发现还有一些day超标,比如说2月份还有29号和30号。干脆在pd.to_datetime()命令中加上errors='coerce'参数,这样在数据超标的地方,时间序列就会变成NaT:

birth["date"]=pd.to_datetime({"year":birth["year"],"month":birth["month"],"day":birth["day"]},errors='coerce')

运行上述命令--->给birth数据文件添加了一列---“date”,记录时间序列。

再次查看birth数据后,发现在day超标的地方,date一列已经标上了NaT(此处只截取部分):

     year  month  day gender  births       date
0 1969 1 1 F 4046 1969-01-01
1 1969 1 1 M 4440 1969-01-01
2 1969 1 2 F 4454 1969-01-02
.. ... ... ... ... ... ...
190 1969 3 99 F 64 NaT
191 1969 3 99 M 50 NaT

现在把date一列中不是null的值提取出来:

birth=birth[birth["date"].notnull()]

接下来制作一个透视表,把1969年-1988年之间各天出生人数的平均数计算出来(此处需要导入numpy):

birth_by_date=pd.pivot_table(birth,values="births",index=["month","day"],aggfunc=np.mean)

birth_by_date透视表如下(此处只截取部分):

             births
month day
1 1 4009.225
2 4247.400
3 4500.900
4 4571.350
5 4603.625
6 4668.150
7 4706.925
8 4629.650
9 4537.775
...
12 2 4830.300
3 4758.500
4 4718.725
5 4734.675
6 4683.050
7 4704.325
8 4803.800
9 4793.825

接下来把透视表的index改为时间序列格式,由于只需要month和day这两项,year先随便填一个:

birth_by_date.index=pd.DatetimeIndex([pd.datetime(2000,month,day) for (month,day) in birth_by_date.index])

现在透视表如下(此处只截取部分):

              births
2000-01-01 4009.225
2000-01-02 4247.400
2000-01-03 4500.900
2000-01-04 4571.350
2000-01-05 4603.625
2000-01-06 4668.150
2000-01-07 4706.925
2000-01-08 4629.650
2000-01-09 4537.775

终于大功告成!真不敢相信,图还没画,处理数据已经花了那么多功夫。但在实际工作中,这其实是很常见的,数据的清洗通常需要花费大部分时间。

接下来以透视表的index为x轴,values为y轴,画折线图:

ax.plot(birth_by_date.index,birth_by_date.values,"-")

图像如下:

Matplotlib学习---用matplotlib画折线图(line chart)

可以看出有几处日期,出生人数骤降,因此需要在这几个地方进行标注。同时,x轴的刻度值要改成12个月份,y轴要加上标签,整个图再拉长一点,再加上标题。这样,这个折线图就基本完美了。

完整代码如下:

import numpy as np
import pandas as pd
import matplotlib as mpl
from matplotlib import pyplot as plt
birth=pd.read_csv(r"https://raw.githubusercontent.com/jakevdp/data-CDCbirths/master/births.csv")
fig,ax=plt.subplots(figsize=(12,4)) birth=birth.iloc[:15067]
birth["day"]=birth["day"].astype(int) birth["date"]=pd.to_datetime({"year":birth["year"],"month":birth["month"],"day":birth["day"]},errors='coerce')
birth=birth[birth["date"].notnull()] birth_by_date=pd.pivot_table(birth,values="births",index=["month","day"],aggfunc=np.mean) birth_by_date.index=pd.DatetimeIndex([pd.datetime(2000,month,day) for (month,day) in birth_by_date.index]) ax.plot(birth_by_date.index,birth_by_date.values,"-")
ax.set(ylabel="average daily births",title="USA births by day of year (1969-1988)",xlim=("2000-01","2001-01"))
ax.grid(True) #显示网格 t=birth_by_date.stack() #把透视表展开 #在相应的地方标上节日名称
ax.text("2000-01-01",t["2000-01-01"],"New Year's Day")
ax.text("2000-07-04",t["2000-07-04"]-60,"Independence Day",ha="center")
ax.text("2000-09-04",t["2000-09-01"]-60,"Labor Day",ha="center")
ax.text("2000-10-31",t["2000-10-31"]-60,"Halloween",ha="center")
ax.text("2000-11-25",t["2000-11-27"]-60,"Thanksgiving",ha="center")
ax.text("2000-12-25",t["2000-12-25"],"Christmas",ha="right") #设置x轴刻度值为月份,并使其居中
ax.xaxis.set_major_locator(mpl.dates.MonthLocator())
ax.xaxis.set_minor_locator(mpl.dates.MonthLocator(bymonthday=15))
ax.xaxis.set_major_formatter(plt.NullFormatter())
ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%h')) plt.show()

最终图像如下:

Matplotlib学习---用matplotlib画折线图(line chart)