这里利用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,"-")
图像如下:
可以看出有几处日期,出生人数骤降,因此需要在这几个地方进行标注。同时,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()
最终图像如下: