113 11 个案例掌握 Python 数据可视化--愿所有人都不被癌症折磨

愿所有人都不被癌症折磨

疾病,尤其是癌症的防治是关乎全世界人民幸福生活的一个巨大主题,本实验通过对美国联邦相关统计部门发布的1999年以来美国人民患癌症情况的调查数据的分析,研究及可视化了美国癌症与年龄、性别等因素的关系。
输入并执行魔法命令 %matplotlib inline, 去除图例、坐标轴右侧、坐标轴顶部边框。

import matplotlib.pyplot as plt
%matplotlib inline

plt.rcParams['legend.frameon'] = False
plt.rcParams['axes.spines.right'] = False
plt.rcParams['axes.spines.top'] = False

准备数据

数据集介绍:
本数据集来自美国疾病防治中心(CDC),美国癌症统计( United States Cancer Statistics,USCS)是自 1999 年,由联邦相关统计部门发起的对全美 51 个州进行的癌症人口数量调查、登记活动,数据真实、可性度高。
导入数据并查看前 5 行。

import pandas as pd
df = pd.read_csv('https://labfile.oss.aliyuncs.com/courses/3023/American_USCS_cancers.txt', sep='\t')
df.head()

从运行结果可知,本数据集主要由癌症种类(Cancer Sites)、性别(Sex)、年龄组(Age Groups)、癌症数量(Count)四个特征构成。

以下对数据进行清洗和处理,主要包括:
对没有癌症数量的行进行删除;
年龄组为离散变量,将其转化成连续变量更有利于分析(此处用正则的方式提取数字后进行均值的计算);
删除癌症种类中处于合并概念的特征,例如 Male and Female Breast 是男性和女性乳腺癌的总和,但癌症特征列分别有男性和女性乳腺癌,因此该特征是对次级特征的重复计数。

import re
import numpy as np
# 挑选出Count列不为空的行
df = df.loc[~pd.isna(df['Count'])]
# 选出需要的特征
df = df[['Cancer Sites', 'Sex', 'Age Groups Code', 'Count']]

# 用正则提取年龄组数据,并进行平均,<1的年龄组平均值取1,>85的年龄组,年龄平均值取85


def get_avg(x):
    min_val, max_val = re.findall('(\d+)[+-]?(\d+)?', x)[0]
    if max_val.isdigit():
        pass
    else:
        max_val = min_val
    return np.mean([float(min_val), float(max_val)])


df['Age Groups Code'] = df['Age Groups Code'].apply(lambda x: get_avg(x))

# 删除以下父级癌症类别

leading_cancer_sites = ['Digestive System',
                        'Male Genital System',
                        'Urinary System',
                        'All Invasive Cancer Sites Combined',
                        'Male and Female Breast',
                        'Male and Female Breast, In Situ',
                        'Male Genital System'
                        'Colon and Rectum',
                        'Lymphomas',
                        'Non-Hodgkin Lymphoma',
                        'Hodgkin Lymphoma',
                        'Colon and Rectum',
                        'Female Genital System',
                        'Brain and Other Nervous System',
                        'Endocrine System',
                        'Leukemias',
                        'Skin excluding Basal and Squamous',
                        'Oral Cavity and Pharynx',
                        'Respiratory System']

for cancer_site in leading_cancer_sites:
    df = df.loc[df['Cancer Sites'] != cancer_site]

df.head()

男性中排名前 10 的癌症类别

对癌症类别按癌症人数进行聚合获得前10的癌症种类,通过 plt.stackplot 接口对其进行堆积面积图的绘制。需要注意的是,与大多数接口不同,plt.stackplot 接口需要一次性传入需要堆积的数据,即,其传入的是需要被可视化序列的列表,因此绘图时传入的数据参数为 data.T 即矩阵数据的转置。

data = df.loc[df['Sex'] == 'Male']
# 男性癌症总数前10项
male_10 = data.groupby(['Cancer Sites'])['Count'].sum(
).sort_values(ascending=False).index[:10]
# 以年龄为行,癌症种类为列,癌症数量为值对数据进行透视,值的计算方程为求和
data = data.pivot_table(values='Count',
                        index='Age Groups Code',
                        aggfunc='mean',
                        columns='Cancer Sites',)
# 选取排名前10的癌症类别
data = data[male_10]
data.head()

运行以下代码完成可视化。

fontsize = 17
plt.rcParams['xtick.labelsize'] = fontsize
plt.rcParams['ytick.labelsize'] = fontsize
plt.rcParams['axes.labelsize'] = fontsize
plt.rcParams['axes.titlesize'] = fontsize
plt.rcParams['legend.fontsize'] = fontsize

plt.rcParams['figure.figsize'] = (15, 6)

plt.stackplot(data.index, data.T, labels=data.columns)
plt.xlabel('Age')
plt.ylabel('Cancer population')

plt.legend(loc=2, ncol=2)
plt.title('The Top 10 Cancers of American Men Varies with Age')

从输出结果可以看出:
大多数的男性癌症从40随开始出现,在 65-75 年龄组人数最多;
男性癌症中,人数最多的是前列腺相关的癌症。

虽然本课程主要是介绍可视化的,但是为了大家的身体健康和癌症预防,我们还是尽量把大家关注的问题细化,以下是前 10 癌症类别的英文对照翻译:

image.png

坐标数值格式化
对于诸如人口一类的数据,由于其数据量纲较大,因此在可视化过程中,其 y 轴显示的数据也较大,会带来一定程度地阅读困难(数字 0 太多,不好数),可以通过以下几种方式解决:
方式一:减小数据量纲并通过 y 轴标题进行单位解释,本例将数据除以 100 万,并在 y 轴轴标题后增加了单位说明。

plt.rcParams['figure.figsize'] = (15, 6)

plt.stackplot(data.index, data.T/1000000, labels=data.columns)
plt.xlabel('Age')
plt.ylabel('Cancer population /million')

plt.legend(loc=2, ncol=2)
plt.title('The Top 10 Cancers of American Men Varies with Age')

方式二:通过格式化文本 FormatStrFormatter 类

from matplotlib.ticker import FormatStrFormatter

plt.rcParams['figure.figsize'] = (15, 6)

plt.stackplot(data.index, data.T, labels=data.columns)
plt.xlabel('Age')
plt.ylabel('Cancer population')

ax = plt.gca()
# 使用科学计数法,减少0的显示
ax.yaxis.set_major_formatter(formatter=FormatStrFormatter('%0.1e'))

plt.legend(loc=2, ncol=2)
plt.title('The Top 10 Cancers of American Men Varies with Age')

方式三:通过格式化函数 FuncFormatter 类

from matplotlib.ticker import FuncFormatter

plt.rcParams['figure.figsize'] = (15, 6)

plt.stackplot(data.index, data.T, labels=data.columns)
plt.xlabel('Age')
plt.ylabel('Cancer population /m:million')

# 定义格式化字符的函数


def major_tick_format(num, pos):
    return '%.1f m' % (num/1000000)


ax = plt.gca()
formatter = FuncFormatter(major_tick_format)
ax.yaxis.set_major_formatter(formatter=formatter)

plt.legend(loc=2, ncol=2)
plt.title('The Top 10 Cancers of American Men Varies with Age')

女性中排名前 10 的癌症类别

筛选出数据集中的女性,进行相同的数据处理。

plt.rcParams['figure.figsize'] = (15, 6)

data = df.loc[df['Sex'] == 'Female']
female_10 = data.groupby(['Cancer Sites'])['Count'].sum(
).sort_values(ascending=False).index[:10]
data = data.pivot_table(values='Count',
                        index='Age Groups Code',
                        aggfunc='sum',
                        columns='Cancer Sites',)
data = data[female_10]

plt.stackplot(data.index, data.T/1000000, labels=data.columns)
plt.xlabel('Age')
plt.ylabel('Cancer population /million')
plt.legend(loc=2, ncol=2)
plt.title('The Top 10 Cancers of American Women Varies with Age')

从输出结果可以看出:
相比于男性,女性患癌症的年龄相对较早,从 30 岁开始癌症人数逐步升高,且年龄分布也相对较宽。
前 10 种癌症中英文对照如下:


image.png

40 岁以前年龄组中排名前15的癌症类别

从所有人群中选出平均年龄小于 40 岁的年龄组,进行相同的数据处理得到排名前 15 的癌症类别。

plt.rcParams['figure.figsize'] = (15, 6)

data = df.loc[df['Age Groups Code'] <= 40]
top_15 = data.groupby(['Cancer Sites'])['Count'].sum(
).sort_values(ascending=False).index[:15]
data = data.pivot_table(values='Count',
                        index='Age Groups Code',
                        aggfunc='sum',
                        columns='Cancer Sites',)
data = data[top_15]


plt.stackplot(data.index, data.T/1000000, labels=data.columns)
plt.xlabel('Age')
plt.ylabel('Cancer population /million')
plt.legend(loc=2, ncol=2)
plt.title('The Top 15 Cancers of American Varies with Age (Less Than 40)')

运行输出结果,结果前 15 癌症类别的中英文翻译为:


image.png

男性各年龄层归一化后前 10 大癌症类别

将数据透视后的数据进行按行(按年龄)的归一化处理,可以得到前 10 癌症在各年龄层不同的患病比例。

plt.rcParams['figure.figsize'] = (9, 12)

data = df.loc[df['Sex'] == 'Male']
male_10 = data.groupby(['Cancer Sites'])['Count'].sum(
).sort_values(ascending=False).index[:10]
data = data.pivot_table(values='Count',
                        index='Age Groups Code',
                        aggfunc='sum',
                        columns='Cancer Sites',)
data = data[male_10]

# 将空值填充为0 ,为空值的数据大多为5岁以下年龄组,查看数据发现该年龄很小概率会患该癌症,所有合理认为其数据为0
data.fillna(value=0, inplace=True)

# 按年龄层进行归一化处理
sums = data.sum(axis=1)
for col in data.columns:
    data[col] = data[col]/sums*100


plt.stackplot(data.index, data.T, labels=data.columns)
plt.xlabel('Age')
plt.ylabel('Cancer population ratio /%')

plt.xlim(0.5, 85)
plt.ylim(0, 100)
plt.legend(bbox_to_anchor=(1.0, 0.7), loc='upper left', ncol=1)
plt.title('The Uniformization Age Cancers of American Men')

从输出结果可以看出:
在小于 20 岁的年龄组,非霍奇金淋巴瘤(NHL-Nodal)的患病概率远高于其他癌症;
40 岁以后,男性患前列腺(Prostate)相关癌症的概率开始显著升高,且在 60 岁达到顶峰;
肺癌和支气管(Lung and Bronchus)是继前列腺相关癌症后,男性的第二大癌症,其分布顶峰也在 60 岁附近;
胰腺癌(Pancreas)的发病随年龄逐渐增加。

女性各年龄层归一化后前 10 大癌症类别

女性数据集做相同处理,得到前 10 癌症在各年龄层不同的患病比例。

plt.rcParams['figure.figsize'] = (9, 12)

data = df.loc[df['Sex'] == 'Female']
female_10 = data.groupby(['Cancer Sites'])['Count'].sum(
).sort_values(ascending=False).index[:10]
data = data.pivot_table(values='Count',
                        index='Age Groups Code',
                        aggfunc='sum',
                        columns='Cancer Sites',)
data = data[female_10]
sums = data.sum(axis=1)
for col in data.columns:
    data[col] = data[col]/sums*100
data.fillna(value=0, inplace=True)

plt.stackplot(data.index, data.T, labels=data.columns)
plt.xlabel('Age')
plt.ylabel('Cancer population ratio /%')

plt.xlim(0.5, 85)
plt.ylim(0, 100)
plt.legend(bbox_to_anchor=(1.0, 0.7), loc='upper left', ncol=1)
plt.title('The Uniformization Age Cancers of American Women')

从输出结果可以看出:
20 岁以前,女性患癌症相对较分散,小于 10 岁时白血病相关(Miscellaneous)较多,一定数量的女性在青春期会患卵巢(Ovary)相关疾病;
甲状腺(Thyroid)相关疾病在 20 岁女性人群中达到较高比例;
乳腺癌从 20 岁开始所占比例逐步上升,在 40 岁左右比例达到峰值;
70 岁左右患肺和呼吸道相关癌症比例仅次于乳腺癌,排名第二。

排名前 10 的癌症类别相对人数对比

上面的三个实验通过堆积面积图的形式可视化数据,可以向读者直观呈现堆积效应和主效应(最大面积块所代表的数据序列为主效应)。正因为如此,细节部分的表述往往一笔带过,堆积的关系无法表达出各系列的绝对差异,plt.fill_between 接口绘制的填充图有效地解决了这个问题。

plt.rcParams['figure.figsize'] = (15, 7)

for sex in ['Male', 'Female']:
    data = df.loc[df['Sex'] == sex]
    male_10 = data.groupby(['Cancer Sites'])['Count'].sum(
    ).sort_values(ascending=False).index[:10]
    data = data.pivot_table(values='Count',
                            index='Age Groups Code',
                            aggfunc='sum',
                            columns='Cancer Sites',)
    data = data[male_10]

    for col in male_10:
        y = data[col]/1000
        plt.fill_between(data.index, y, alpha=0.3)
        plt.plot(data.index, y, lw=6, label=col,)

    plt.xlabel('Age')
    plt.ylabel('Cancer population /k')
    plt.legend(loc=2, ncol=2)
    plt.title('The Top 10 cancers in American Varies with Age for %s' % (sex))

从运行结果可以看出,男性与女性前 10 类癌症在各年龄段的绝对数量比堆积面积图更为清晰,表现在:
各类癌症随年龄的变化趋势和相对差异更为清晰。剪除了堆积效应,可以更直观地发现,男性患病年龄在 45 岁出现了一个拐点,女性在 35 岁左右;
男性的主要癌症集中在前列腺癌,其总人数峰值在 70 岁左右,女性主要是乳腺癌,主要患病年龄段跨度较大 40 - 80 岁,峰值出现在 65 岁左右;
男性和女性第二大癌症均为肺及支气管相关癌症,且患病年龄较为一致。

患癌症年龄的性别对比

从性别的角度按年龄对患癌症人数进行聚合。

plt.rcParams['figure.figsize'] = (10, 6)

data = df
data = data.pivot_table(values='Count',
                        index='Age Groups Code',
                        aggfunc='sum',
                        columns='Sex',)


colors = {'Male': 'tab:blue', 'Female': 'tab:red'}
for col in data.columns:
    color = colors[col]
    plt.fill_between(data.index, data[col]/1000000, alpha=0.3, color=color)
    plt.plot(data.index, data[col]/1000000, lw=6, label=col, color=color)

plt.xlabel('Age')
plt.ylabel('Cancer population /million')

plt.legend(loc=2, ncol=3)
plt.title('The Cancers population varies by gender and age')

从运行结果可以看出,男性患癌症的年龄分布更趋向于高龄且更为集中,而女性则分布相对均匀,人数拐点早于男性。

前20大癌症在男女性别上的分布差异

从实验「排名前 10 的癌症类别相对人数对比」可知,男女性在癌症年龄表现上有较大差异,以下进一步研究在前 20 大癌症上,性别对患癌症有怎样的影响。
画布生成 4 * 5 总计 20 个子图,每个子图可视化 1 种癌症的性别差异。

fontsize = 14
# 由于子图相对较小,将全图字号减小至 14 号
plt.rcParams['xtick.labelsize'] = fontsize
plt.rcParams['ytick.labelsize'] = fontsize
plt.rcParams['axes.labelsize'] = fontsize
plt.rcParams['axes.titlesize'] = fontsize
plt.rcParams['legend.fontsize'] = fontsize

rows, cols = 4, 5

top_ = df.groupby(['Cancer Sites'])['Count'].sum(
).sort_values(ascending=False).index[:rows*cols]

fig, axs = plt.subplots(rows, cols, sharex=True, figsize=(3.8*cols, 3.0*rows))
axs = axs.ravel()

for i, cancer in enumerate(top_):
    ax = axs[i]
    data = df.loc[df['Cancer Sites'] == cancer]
    data = data.pivot_table(values='Count',
                            index='Age Groups Code',
                            aggfunc='sum',
                            columns='Sex',)
    data = data/100000

    # 不同性别绘制不同的颜色
    colors = {'Male': 'tab:blue', 'Female': 'tab:red'}
    for col in data.columns:
        color = colors[col]
        ax.fill_between(data.index, data[col], alpha=0.3, color=color)
        ax.plot(data.index, data[col], lw=6, label=col, color=color)
        ax.set_title(cancer)

        # 第二张图绘制图例,原因是第一张图只有女性,图例不够完全
        if i == 1:
            ax.legend()
        # 子图的最后一行添加 x 坐标轴标题
        if i >= (rows-1)*cols:
            ax.set_xlabel('Age')
        # 子图的第一列添加 y 坐标轴标题
        if i % cols == 0:
            ax.set_ylabel('Cancer population/hk')

fig.suptitle(
    'The Top %d Cancers Population Varies With Gender and Age' % (rows*cols), va='top', size=22)
# 堆积 y 轴坐标轴标题
fig.align_ylabels()
# 调节子图的宽度和高度间距
plt.subplots_adjust(wspace=0.2, hspace=0.3)

从运行结果可以看出:
显而易见地,部分疾病有性别差异,例如乳腺、卵巢相关癌症只分布在女性,前列腺相关疾病只分布在男性;
排名第二的肺及支气管相关癌症,在性别上随年龄患病人数趋势大致相同;
泌尿系统(Urinary)、肝脏(Liver)相关疾病,男性显著地高于女性;
甲状腺(Thyroid)类相关疾病,女性显著地高于男性。

小于20岁年龄组在男女性别上的分布差异

从实验「排名前 10 的癌症类别相对人数对比」可知,男女性在癌症年龄表现上有较大差异,由于小于20岁年龄组癌症人数较少,其性别差异在整体图上并不明显,以下进一步研究在前 20 大癌症上,低年龄组人群种,性别对患癌症有怎样的影响。

rows = 4
cols = 5
limit_age = 20

top_ = df.loc[df['Age Groups Code'] <= limit_age].groupby(
    ['Cancer Sites'])['Count'].sum().sort_values(ascending=False).index[:rows*cols]

fig, axs = plt.subplots(rows, cols, sharex=True, figsize=(3.8*cols, 3.0*rows))
axs = axs.ravel()

for i, cancer in enumerate(top_):
    ax = axs[i]
    data = df.loc[df['Age Groups Code'] <= limit_age]
    data = data.loc[data['Cancer Sites'] == cancer]
    data = data.pivot_table(values='Count',
                            index='Age Groups Code',
                            aggfunc='sum',
                            columns='Sex',)
    data = data/10000

    colors = {'Male': 'tab:blue', 'Female': 'tab:red'}
    for col in data.columns:
        color = colors[col]
        ax.fill_between(data.index, data[col], alpha=0.3, color=color)
        ax.plot(data.index, data[col], lw=6, label=col, color=color)
        ax.set_title(cancer)
        if i == 0:
            ax.legend()
        if i >= (rows-1)*cols:
            ax.set_xlabel('Age')
        if i % cols == 0:
            ax.set_ylabel('Cancer population /W')
fig.suptitle(
    'The Top %d Cancers Population Varies With Gender and Age(Less Than %d)' % (rows*cols, limit_age), va='top', size=22)
fig.align_ylabels()
plt.subplots_adjust(wspace=0.2, hspace=0.3)

从运行结果可以看出:
低年龄组中,除生殖系统(Testis、Ovary)相关疾病以外,在大多数癌症上并未体现出性别差异;
男性在以 NHL-Nodal 和 NHL-Extranodal 为代表的非霍奇金淋巴瘤(Non-Hodgkin Lymphoma,强两者为其子类)上显著高于女性。

可视化分层年龄组的绝对差异

上面的实验可视化了任意年龄(连续型数据)的性别差异,而对于组间差异,面积或填充图无法可视化。原因是:
虽然数据可视化了连续区间任意位置的数据,但是没有办法获取任意区间(采样层)的聚合特征(常用均值、中位数等表达)和离散特征(常用标准偏差等表达);
任意点的高低绝对水平并不能代表整体高低水平,无法进行显著性差异对比;
常用于进行分层差异比较的绘图对象为哑铃图、直方图、箱线图、误差线图等。
哑铃图绘制原理
哑铃图有一根带有两端标记的直线构成,因其形状类似“哑铃”而得名,其绘制原理是通过 plt.scatter 散点图接口绘制端点的标记,通过辅助线 plt.vlines 绘制中间的直线。 哑铃图上部点表示的数据为该采样层 均值 + 标准偏差,下部点表示 均值 - 标准偏差。 两根哑铃图之间绘制填充面,填充面颜色根据两端聚合结果的绝对值大小分别赋值不同的颜色。 其过程为:
准备数据

data = df
# 获得所有癌症,性别的均值和标准偏差
data = data.groupby(['Cancer Sites', 'Sex'], as_index=False)[
    'Count'].agg(['mean', 'std'])

data['Cancer Sites'] = data.index.droplevel(level=1)
data['Sex'] = data.index.droplevel(level=0)
# 查看数据前 5 行
data.head()

获取某一个癌症类在不同性别人群的均值和标准偏差数据。

tmp = data.loc[data['Cancer Sites'] == 'Acute Lymphocytic Leukemia']
f_mean, f_std, m_mean, m_std = np.array(tmp[['mean', 'std']]).ravel()
f_mean, f_std, m_mean, m_std

绘制哑铃图。

s = 80
plt.scatter(0, f_mean+f_std, color='tab:red', s=s)
plt.scatter(0, f_mean-f_std, color='tab:red', s=s)
plt.scatter(1, m_mean+m_std, color='tab:blue', s=s)
plt.scatter(1, m_mean-m_std, color='tab:blue', s=s)
plt.vlines(0, ymin=f_mean-f_std, ymax=f_mean+f_std, lw=4, color='tab:red')
plt.vlines(1, ymin=m_mean-m_std, ymax=m_mean+m_std, lw=4, color='tab:blue')

绘制填充面。

plt.scatter(0, f_mean+f_std, color='tab:red', s=s)
plt.scatter(0, f_mean-f_std, color='tab:red', s=s)
plt.scatter(1, m_mean+m_std, color='tab:blue', s=s)
plt.scatter(1, m_mean-m_std, color='tab:blue', s=s)
plt.vlines(0, ymin=f_mean-f_std, ymax=f_mean+f_std, lw=4, color='tab:red')
plt.vlines(1, ymin=m_mean-m_std, ymax=m_mean+m_std, lw=4, color='tab:blue')

# 绘制填充面
# 将哑铃图上部、下面两个点进行线性拟合
f_top = np.polyfit(x=[0, 1], y=[f_mean+f_std, m_mean+m_std], deg=1)
f_bottom = np.polyfit(x=[0, 1], y=[f_mean-f_std, m_mean-m_std], deg=1)

# 生成填充面的 x,y 坐标
xs = np.linspace(0, 1, 100)
y_tops = np.polyval(f_top, xs)
y_bottoms = np.polyval(f_bottom, xs)

# 根据均值,确定填充面填充颜色
if np.sum(f_mean > m_mean):
    plt.fill_between(xs, y_tops, y_bottoms, color='tab:red', alpha=0.6)
else:
    plt.fill_between(xs, y_tops, y_bottoms, color='tab:blue', alpha=0.6)

年龄组 < 20 岁
根据以上过程,获得年龄组在 20 岁以下人群在前 20 项共有癌症上的组间差异。

fontsize = 16
plt.rcParams['xtick.labelsize'] = fontsize
plt.rcParams['ytick.labelsize'] = fontsize
plt.rcParams['axes.labelsize'] = fontsize
plt.rcParams['axes.titlesize'] = fontsize
plt.rcParams['legend.fontsize'] = fontsize


rows, cols = 4, 5
limit_age = 20

top_df = df.loc[df['Age Groups Code'] <= limit_age].pivot_table(
    values='Count', index='Cancer Sites', columns='Sex',)
top_df['sum'] = top_df.sum(axis=1)
top_df.dropna(inplace=True)
top_df = top_df.sort_values(['sum'], ascending=False)
top_ = top_df.index[:rows*cols]


data = df.loc[df['Age Groups Code'] <= limit_age]

data = data.groupby(['Cancer Sites', 'Sex'], as_index=False)[
    'Count'].agg(['mean', 'std'])
data = data/1000
data['Cancer Sites'] = data.index.droplevel(level=1)
data['Sex'] = data.index.droplevel(level=0)


fig, axs = plt.subplots(rows, cols, sharex=True, figsize=(3.8*cols, 3.0*rows))
axs = axs.ravel()

for i, cancer in enumerate(top_):
    ax = axs[i]
    tmp = data.loc[data['Cancer Sites'] == cancer]

    f_mean, f_std, m_mean, m_std = np.array(tmp[['mean', 'std']]).ravel()

    # 哑铃图
    s = 80
    ax.scatter(0, f_mean+f_std, color='tab:red', s=s)
    ax.scatter(0, f_mean-f_std, color='tab:red', s=s)
    ax.scatter(1, m_mean+m_std, color='tab:blue', s=s)
    ax.scatter(1, m_mean-m_std, color='tab:blue', s=s)
    ax.vlines(0, ymin=f_mean-f_std, ymax=f_mean+f_std, lw=4, color='tab:red')
    ax.vlines(1, ymin=m_mean-m_std, ymax=m_mean+m_std, lw=4, color='tab:blue')

    ax.set_xticks([0, 1])
    ax.set_xticklabels(['Female', 'Male'])
    ax.set_xlim(-0.3, 1.2)

    # 面积图
    f_top = np.polyfit(x=[0, 1], y=[f_mean+f_std, m_mean+m_std], deg=1)
    f_bottom = np.polyfit(x=[0, 1], y=[f_mean-f_std, m_mean-m_std], deg=1)
    xs = np.linspace(0, 1, 100)
    y_tops = np.polyval(f_top, xs)
    y_bottoms = np.polyval(f_bottom, xs)

    if np.sum(f_mean > m_mean):
        ax.fill_between(xs, y_tops, y_bottoms, color='tab:red', alpha=0.6)
    else:
        ax.fill_between(xs, y_tops, y_bottoms, color='tab:blue', alpha=0.6)

    ax.set_title(cancer)

    if i >= (rows-1)*cols:
        ax.set_xlabel('Gender')
    if i % cols == 0:
        ax.set_ylabel('Cancer population/k', size=14)

fig.suptitle(
    'The Top %d Cancers Population Varies With Gender and Age(Less Than %d)' % (rows*cols, limit_age), va='top', size=22)
fig.align_ylabels()
plt.subplots_adjust(wspace=0.2, hspace=0.3)

从输出结果可以看出:
对于排名前 20 的癌症,在本年龄组,男性患病人数均值大多数高于女性均值(蓝色填充面的子图多于红色填充面);
对于大多数癌症,性别差异并不明显,表现出四边形比较接近于矩形;
甲状腺类癌症,女性要显著性地高于男性。
年龄组 > 50 岁
同理可研究年龄组大于 50 岁人群,男性在肝脏(Liver)、直肠(Rectum)等方面的癌症显著性地高于女性。

rows, cols = 4, 5
limit_age = 50

# 高年龄段由于某些癌症只有一个性别有,因此此处将其进行了筛选
top_df = df.loc[df['Age Groups Code'] >= limit_age].pivot_table(
    values='Count', index='Cancer Sites', columns='Sex',)
top_df['sum'] = top_df.sum(axis=1)
top_df.dropna(inplace=True)
top_df = top_df.sort_values(['sum'], ascending=False)
top_ = top_df.index[:rows*cols]

data = df.loc[df['Age Groups Code'] >= limit_age]
data = data.groupby(['Cancer Sites', 'Sex'], as_index=False)[
    'Count'].agg(['mean', 'std'])
data = data/1000
data['Cancer Sites'] = data.index.droplevel(level=1)
data['Sex'] = data.index.droplevel(level=0)


fig, axs = plt.subplots(rows, cols, sharex=True, figsize=(3.8*cols, 3.0*rows))
axs = axs.ravel()

for i, cancer in enumerate(top_):
    ax = axs[i]
    tmp = data.loc[data['Cancer Sites'] == cancer]

    f_mean, f_std, m_mean, m_std = np.array(tmp[['mean', 'std']]).ravel()

    # 哑铃图
    s = 80
    ax.scatter(0, f_mean+f_std, color='tab:red', s=s)
    ax.scatter(0, f_mean-f_std, color='tab:red', s=s)
    ax.scatter(1, m_mean+m_std, color='tab:blue', s=s)
    ax.scatter(1, m_mean-m_std, color='tab:blue', s=s)
    ax.vlines(0, ymin=f_mean-f_std, ymax=f_mean+f_std, lw=4, color='tab:red')
    ax.vlines(1, ymin=m_mean-m_std, ymax=m_mean+m_std, lw=4, color='tab:blue')

    ax.set_xticks([0, 1])
    ax.set_xticklabels(['Female', 'Male'])
    ax.set_xlim(-0.3, 1.2)

    # 面积图
    f_top = np.polyfit(x=[0, 1], y=[f_mean+f_std, m_mean+m_std], deg=1)
    f_bottom = np.polyfit(x=[0, 1], y=[f_mean-f_std, m_mean-m_std], deg=1)
    xs = np.linspace(0, 1, 100)
    y_tops = np.polyval(f_top, xs)
    y_bottoms = np.polyval(f_bottom, xs)

    if np.sum(f_mean > m_mean):
        ax.fill_between(xs, y_tops, y_bottoms, color='tab:red', alpha=0.6)
    else:
        ax.fill_between(xs, y_tops, y_bottoms, color='tab:blue', alpha=0.6)

    ax.set_title(cancer)

    if i >= (rows-1)*cols:
        ax.set_xlabel('Gender')
    if i % cols == 0:
        ax.set_ylabel('Cancer population/k', size=14)

fig.suptitle(
    'The Top %d Cancers Population Varies With Gender(Age More Than %d)' % (rows*cols, limit_age), va='top', size=22)
fig.align_ylabels()
plt.subplots_adjust(wspace=0.2, hspace=0.3)

年龄组 20 - 40 岁之间
为增加代码可读性,可单独创建哑铃图的绘图函数。

def dumbbell(f_mean, f_std, m_mean, m_std, ax):
    # 哑铃图
    s = 80
    ax.scatter(0, f_mean+f_std, color='tab:red', s=s)
    ax.scatter(0, f_mean-f_std, color='tab:red', s=s)
    ax.scatter(1, m_mean+m_std, color='tab:blue', s=s)
    ax.scatter(1, m_mean-m_std, color='tab:blue', s=s)
    ax.vlines(0, ymin=f_mean-f_std, ymax=f_mean+f_std, lw=4, color='tab:red')
    ax.vlines(1, ymin=m_mean-m_std, ymax=m_mean+m_std, lw=4, color='tab:blue')

    ax.set_xticks([0, 1])
    ax.set_xticklabels(['Female', 'Male'])
    ax.set_xlim(-0.3, 1.2)

    # 面积图
    f_top = np.polyfit(x=[0, 1], y=[f_mean+f_std, m_mean+m_std], deg=1)
    f_bottom = np.polyfit(x=[0, 1], y=[f_mean-f_std, m_mean-m_std], deg=1)
    xs = np.linspace(0, 1, 100)
    y_tops = np.polyval(f_top, xs)
    y_bottoms = np.polyval(f_bottom, xs)

    if np.sum(f_mean > m_mean):
        ax.fill_between(xs, y_tops, y_bottoms, color='tab:red', alpha=0.6)
    else:
        ax.fill_between(xs, y_tops, y_bottoms, color='tab:blue', alpha=0.6)

    return ax

运行以下代码,获得年龄组 20 - 40 区间的组间差异图。

rows, cols = 4, 5
min_age, max_age = 20, 50

# 高年龄段由于某些癌症只有一个性别有,因此此处将其进行了筛选
top_df = df.loc[(df['Age Groups Code'] >= min_age) & (df['Age Groups Code'] <=
                                                      max_age)].pivot_table(values='Count', index='Cancer Sites', columns='Sex',)
top_df['sum'] = top_df.sum(axis=1)
top_df.dropna(inplace=True)
top_df = top_df.sort_values(['sum'], ascending=False)
top_ = top_df.index[:rows*cols]

data = df.loc[(df['Age Groups Code'] >= min_age) &
              (df['Age Groups Code'] <= max_age)]
data = data.groupby(['Cancer Sites', 'Sex'], as_index=False)[
    'Count'].agg(['mean', 'std'])
data = data/1000
data['Cancer Sites'] = data.index.droplevel(level=1)
data['Sex'] = data.index.droplevel(level=0)


fig, axs = plt.subplots(rows, cols, sharex=True, figsize=(3.8*cols, 3.0*rows))
axs = axs.ravel()

for i, cancer in enumerate(top_):
    ax = axs[i]
    tmp = data.loc[data['Cancer Sites'] == cancer]
    f_mean, f_std, m_mean, m_std = np.array(tmp[['mean', 'std']]).ravel()

    ax = dumbbell(f_mean, f_std, m_mean, m_std, ax)

    ax.set_title(cancer)

    if i >= (rows-1)*cols:
        ax.set_xlabel('Gender')
    if i % cols == 0:
        ax.set_ylabel('Cancer population/k', size=14)

fig.suptitle(
    'The Top %d Cancers Population Varies With Gender(Age Between %d And %d)' % (rows*cols, min_age, max_age), va='top', size=22)
fig.align_ylabels()
plt.subplots_adjust(wspace=0.2, hspace=0.3)

你可能感兴趣的:(113 11 个案例掌握 Python 数据可视化--愿所有人都不被癌症折磨)