峰峦图
峰峦图是一种展示数据核密度的图形方法,由于各种数据前后重叠像一重重山川而得名。峰峦图可以通过seaborn、joypy实现,也可以通过matplotlib实现。下面简单介绍这三种种峰峦图的统计方法。
matplotlib实现峰峦图
峰峦图实际上是一种统计图,不展示原始数据。所以我们需要先对数据进行统计。下面是虚构的一个站点的全年逐日平均数据:
假设我们试图展示逐月的日平均气温核密度,应该首先将这些数据按照月份分组(groupby),然后将平均气温分箱后(cut),统计各气温区间的分布数量(value_counts)。
首先是将时间列作为分组依据,调用dt属性后,使用month分组,这样我们将得到1-12月份的分组:
df.groupby(df['时间'].dt.month).groups.keys()
out_put:→dict_keys([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
我们可以使用for循环对每个分组操作,但pandas向我们提供了一个广播函数到所有分组的方法apply,所以我们到时候使用这个方法。
我们需要定义一个广播函数,我们将他命名为stats_temp:
def stats_temp(dataframe):
#对输入的DataFrame分箱:
cut_df=pd.cut(dataframe['平均气温'],bins=np.arange(-5,31,1))
#统计各箱体气温数量
stats_df=cut_df.value_counts(sort=False)
return stats_df
对所有分组使用该函数后,我们将得到各月份的,各气温阶段的落区统计(非常有用的技巧,其他时间序列也可以通过这种方法快速统计,定义一个不错的函数在各分组apply,比for循环快还方便):
df.groupby(df['时间'].dt.month).apply(stats_temp)
由于有十二个月,相当于要生成十二个ax,fig.add_axes或者subplot一个个添加太慢了,所以我们选择subplots:
fig,axes=plt.subplots(12,1)
再进行方方面面的美化修饰参数,并调节每张子图的垂直距离为负数,然后在每张ax上逐月绘制图像,通过plot和fill_between绘制峰峦。在循环中解除每张子图的axis,这样每张图像都不会产生遮挡。在运行到最后一张子图时,添加横坐标的气温坐标:
plt.rcParams['font.sans-serif']=['FangSong']
colors=mpl.colormaps['Spectral'].resampled(12)(range(12))
fig,axes=plt.subplots(12,1,**{'figsize':(5,3),'dpi':500})
fig.subplots_adjust(hspace=-0.7)
fig.suptitle(x=0.5,y=0.9,t='各月气温核密度分布',fontsize=10)
for i,ax in enumerate(axes.flatten()):
ax.text(-2,0.5,'{}月'.format(i+1),fontsize=8)
ax.plot(range(len(df_counts.iloc[i].values)),df_counts.iloc[i].values,lw=0.5,c=colors[i])
ax.fill_between(range(len(df_counts.iloc[i].values)),
df_counts.iloc[i].values,
y2=0,
where=(df_counts.iloc[i].values>0),
interpolate=True,color=colors[i])
ax.axis('off')
if i==11:
ax.set_xticks(range(len(df_counts.iloc[i].values))[::4])
ax.set_xticklabels(np.arange(-5,31,1)[1::4])
ax.axis('on')
ax.spines[:].set_visible(False)
ax.set_facecolor('none')
ax.tick_params(labelsize=6,width=0.5)
ax.yaxis.set_major_locator(mticker.NullLocator())
ax.set_xlabel('气温',fontsize=6)
这里由于给出的cut分箱较大,同时样本量较少,锯齿较严重,但原理是不变的。也可以使用专门的核密度估计函数。这里是展示峰峦图的绘图原理。掌握了原理是不怕数据变的。
seaborn实现峰峦图
seaborn本来就是matplotlib的高级封装,提供了专门的命令来绘制图像,同时官网也有demo。
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style="white", rc={"axes.facecolor": (0, 0, 0, 0)})
# Create the data
rs = np.random.RandomState(1979)
x = rs.randn(500)
g = np.tile(list("ABCDEFGHIJ"), 50)
df = pd.DataFrame(dict(x=x, g=g))
m = df.g.map(ord)
df["x"] += m
# Initialize the FacetGrid object
pal = sns.cubehelix_palette(10, rot=-.25, light=.7)
g = sns.FacetGrid(df, row="g", hue="g", aspect=15, height=.5, palette=pal)
# Draw the densities in a few steps
g.map(sns.kdeplot, "x",
bw_adjust=.5, clip_on=False,
fill=True, alpha=1, linewidth=1.5)
g.map(sns.kdeplot, "x", clip_on=False, color="w", lw=2, bw_adjust=.5)
# passing color=None to refline() uses the hue mapping
g.refline(y=0, linewidth=2, linestyle="-", color=None, clip_on=False)
# Define and use a simple function to label the plot in axes coordinates
def label(x, color, label):
ax = plt.gca()
ax.text(0, .2, label, fontweight="bold", color=color,
ha="left", va="center", transform=ax.transAxes)
g.map(label, "x")
# 这一步其实也就是我们调节每个子图的命令,不难看出seaborn和matplotlib思路是一样的
g.figure.subplots_adjust(hspace=-.25)
# Remove axes details that don't play well with overlap
g.set_titles("")
g.set(yticks=[], ylabel="")
g.despine(bottom=True, left=True)
joypy实现峰峦图
joypy是专门绘制峰峦图的库。joypy自带核密度估计,我们只需要给出分组即可,不用另外进行统计。
import joypy
fig,axes=joypy.joyplot(df.groupby(df['时间'].dt.month))