谁在管理我们的国家?——2019年全国人民代表大会数据分析与可视化

谁在管理我们的国家?——2019年全国人民代表大会数据分析与可视化

本周的小项目作业是一个探索性数据分析的案例。

CGTN 的网站上有一个非常炫酷的项目,是关于 2019 年的全国人民代表大会参会人大代表数据的一个可视化:https://news.cgtn.com/event/2019/whorunschina/index.html ,里面从下面的角度探索了这个数据的一些特征:

  1. 参会人员数量:2975 人;
  2. 男女比例:男性 2233 人,女性 742 人;
  3. 年龄分布,90 后 28 个人;
  4. 民族分布:汉族 2538 人;
  5. 学历分布:硕士最多,836 人;
  6. 学科分布:看起来还是经管专业的人最多;
  7. 党派分布:CPC 最多,2172 人;

后面还有好几张图我没有整理数据,所以我们先关注前 7 个图。本次的作业就是对我提供的 NPC.csv 数据进行探索性数据分析,这个数据大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
library(readr)
library(magrittr)
npc <- read_csv('NPC.csv')
npc %>%
select(1:5) %>%
head(5) %>%
DT::datatable(
extensions = 'Buttons',
options = list(dom = 'Blfrtip',
buttons = c('copy', 'csv', 'excel',
'pdf', 'print'),
lengthMenu = list(c(10, 25, 50, -1),
c(10, 25, 50, "All"))))

例如学科的分布:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# devtools::install_github('erocoar/ggparl')
library(ggparl)
library(hrbrthemes)
npc %>% count(人文社科拆后专业) %$%
ggplot_parliament(.$人文社科拆后专业, .$n) +
theme_ipsum(base_family = 'STYuanti-SC-Regular') +
theme(axis.text = element_blank()) +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.title = element_blank(),
panel.background = element_blank()) +
scale_fill_manual(name = "学科",
values = c(brewer.pal(9, 'Paired'),
brewer.pal(5, "Set2"))) -> majors

majors$guides$fill$title <- "学科"
majors

作业

本周的作业就是对我提供的 NPC.csv 数据进行探索性数据分析,绘制出 7 幅图表并作出一定的解读。

分析工具任选,推荐使用 R、Stata 和 Python;

如果你打算使用 R 语言,可以参考下面的 tips:

  1. 数据处理包:dplyr
  2. 绘图包:ggplot2
  3. 绘制华夫图:waffleggwaffle
  4. 绘制树图:treemapify
  5. 绘制桑基图:sankeywheelggalluvial
  6. 绘制地图:hchinamap、ggplot + sf
  7. 绘制席位分摊图:ggparlggparliamentggpol

Happy Tidying Data on Friday ~

参考结果

我们可以通过下面几个纬度对这个数据集进行分析,首先读取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
library(tidyverse)
df <- read_csv('NPC.csv')
# 概览数据:
glimpse(df)

#> Observations: 2,975
#> Variables: 25
#> $ Delegation <chr> "Anhui", "Hunan", "Shaanxi", "Fujian", …
#> $ Partisan <chr> "CPC(Communist Party of China)", "CPC(C…
#> $ 党派 <chr> "中共", "中共", "中共", "无", "农工党", "民主同盟", "民主…
#> $ Name <chr> "Ding Shiqi", "Ding Xiaobing", "Ding Yu…
#> $ 姓名 <chr> "丁士启", "丁小兵", "丁云祥", "丁世忠", "丁列明", "丁光宏",…
#> $ Gender <chr> "Male", "Male", "Male", "Male", "Male",…
#> $ 性别 <chr> "男", "男", "男", "男", "男", "男", "男", "男", "…
#> $ `Birth year` <dbl> 1966, 1960, 1963, 1970, 1963, 1963, 195…
#> $ Age <dbl> 53, 59, 56, 49, 56, 56, 62, 62, 52, 49,…
#> $ Generation <chr> "1960s", "1960s", "1960s", "1970s", "19…
#> $ 年代 <chr> "60后", "60后", "60后", "70后", "60后", "60后",…
#> $ Ethnicity <chr> "Han", "Han", "Han", "Hui", "Han", "Han…
#> $ 民族 <chr> "汉族", "汉族", "汉族", "回族", "汉族", "汉族", "汉族",…
#> $ Birthplace <chr> "Anhui", "Gansu", "Shaanxi", "Fujian", …
#> $ 籍贯 <chr> "安徽", "甘肃", "陕西", "福建", "浙江", "安徽", "浙江",…
#> $ Region <chr> "Central China", "Western China", "West…
#> $ 区域 <chr> "中部", "西部", "西部", "东部", "东部", "中部", "东部",…
#> $ `Subject Department` <chr> "Natrual Sciences", "Unknown", "Natrual…
#> $ 专业分类 <chr> "自然科学", "未知", "自然科学", "社会科学", "自然科学", "自然科学…
#> $ Major <chr> "Engineering", "Unknown", "Engineering"…
#> $ 人文社科拆后专业 <chr> "工学", "未知", "工学", "管理学", "医学", "理学", "理学", "未知"…
#> $ `Educational background` <chr> "PhD", "Unknown", "PhD", "Bachelor", "P…
#> $ 学历 <chr> "博士研究生", "未知", "博士研究生", "本科", "博士研究生", "硕…
#> $ `Ever studied abroad` <chr> "No", "Unknown", "No", "No", "Yes", "No…
#> $ 海外留学经验 <chr> "无", "未知", "无", "无", "有", "无", "无", "无", "无",…

总人数

1
2
3
4
5
df %>% 
count() %>%
pull()

#> [1] 2975

计算每个代表团的总人数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
df %>% 
count(Delegation)

#> # A tibble: 35 x 2
#> Delegation n
#> <chr> <int>
#> 1 Anhui 111
#> 2 Beijing 54
#> 3 Chongqing 60
#> 4 Fujian 69
#> 5 Gansu 52
#> 6 Guangdong 163
#> 7 Guangxi 89
#> 8 Guizhou 73
#> 9 Hainan 25
#> 10 Hebei 123
#> # … with 25 more rows

我手动把这个结果翻译了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(delegation <- read_csv("delegation2.csv") %>%
transmute(
delegation = `代表团`,
count = n
) %>%
mutate(
delegation = gsub(x = delegation, pattern = "中国人民解放军武装警察部队", replacement = "中国人民解放军\n武装警察部队")
))

#> # A tibble: 35 x 2
#> delegation count
#> <chr> <dbl>
#> 1 安徽 111
#> 2 北京 54
#> 3 重庆 60
#> 4 福建 69
#> 5 甘肃 52
#> 6 广东 163
#> 7 广西 89
#> 8 贵州 73
#> 9 海南 25
#> 10 河北 123
#> # … with 25 more rows

然后我们绘制一幅华夫图展示这个结果,waffle 包返回的是一个 gg 对象,所以我们可以在其上直接添加 ggplot2 包的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
library(waffle)
library(ggpomological)
slfont <- "STLibianTC-Regular"
colors <- rep(ggpomological:::pomological_palette, 4)[1:35]
# 得先把 delegation 转换成 named vector
dv <- delegation$count
names(dv) <- delegation$delegation
waffle(dv, rows = 40, size = 0.5, colors = colors) +
labs(title = "全国人大代表人数:2975",
subtitle = "一共35个代表团",
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>") +
theme_pomological(base_family = slfont,
base_size = 15) +
theme(axis.text = element_blank(),
panel.grid.major = element_blank(),
legend.position = "none")

因为代表团有 35 个,要是显示图例的话就太拥挤了,所以我把图例取消了,然后上面的华夫图的填充色就仅仅是为了美观了,但是也能反映一定的信息,例如可以看出各个代表团的人数还是蛮均衡的。我们还可以把人数最多的 9 个代表团拿出来比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
library(forcats)
library(ggchicklet)
# 这九个代表团的总人数:
delegation %>%
arrange(desc(count)) %>%
slice(1:9) %>%
summarise(total = sum(count)) %>%
pull()

#> [1] 1454

# 绘制一幅条形统计图:
delegation %>%
arrange(desc(count)) %>%
slice(1:9) %>%
mutate(
delegation = stringr::str_replace_all(delegation,
"中国人民解放军武警部队",
"中国人民\n解放军\n武警部队")
) %>%
mutate(
delegation = fct_reorder(delegation, count)
) %>%
ggplot() +
geom_chicklet(aes(x = delegation, y = count,
fill = delegation, color = delegation),
radius = grid::unit(0.3, "cm")) +
scale_fill_pomological() +
scale_color_pomological() +
theme_pomological(base_family = slfont,
base_size = 14) +
labs(x = "", y = "人数",
title = "全国人民代表大会人数最多的九个代表团",
subtitle = "这九个代表团的总人数为 1454 ",
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>") +
coord_flip() +
theme(legend.position = "none")

性别、年龄分布

性别年龄分布我们可以一起来看:

1
2
3
4
5
6
7
8
9
10
11
df %>% count(`年代`, `性别`) %>%
ggplot() +
geom_col(aes(x = `年代`, y = n, fill = `性别`, color = `性别`)) +
labs(x = "年代", y = "人数", title = "全国人大代表的年龄分布",
subtitle = paste("平均年龄为", df %$% mean(.$Age, na.rm = T) %>% round(2), "岁"),
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>") +
theme_pomological(base_family = slfont,
base_size = 14) +
scale_fill_pomological("性别") +
scale_color_pomological("性别") +
theme(legend.position = c(0.15, 0.8))

可以看出,60 后是 NPC 的核心,90 后中女性的数量多于男性。人大代表们的平均年龄是 53.77 岁。其中,1672 名代表出生于 20 世纪 60 年代,占总数的一半以上。另外我们还可以看到,代表们越年轻,性别比例越均衡。

是不是觉得女性的比例很少?实际上近几届人大会议上女代表的比例正在稳步上升:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
library(scatterpie)
wm <- tribble(
~year, ~woman,
10, 0.202,
11, 0.213,
12, 0.234,
13, 0.249
) %>%
mutate(
man = 1 - woman
)
ggplot() +
geom_scatterpie(aes(x = year, y = 5,
group = year),
cols = c("woman", "man"),
data = wm,
pie_scale = 5,
color = "#FFFEEA") +
coord_equal() +
theme_pomological(base_family = slfont,
base_size = 14) +
scale_x_continuous(breaks = 10:13,
labels = paste0(10:13, "th")) +
scale_fill_pomological("性别",
breaks = c("man", "woman"),
labels = c("男性", "女性")) +
theme(axis.title.y = element_blank(),
axis.text.y = element_blank()) +
labs(x = "全国人大\n\n",
title = "历届全国人大女性代表的比例变化",
subtitle = "第 13 届全国人大女性代表的比例为 24.9%,比第 11 届提高了 4.7 个百分点\n\n\n",
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>")

民族分布

中国是个有着 56 个民族的多民族国家,55个少数民族 + 汉族,那么人大代表中有多少个民族呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
df %>%
count(民族)

#> # A tibble: 56 x 2
#> 民族 n
#> <chr> <int>
#> 1 东乡族 2
#> 2 乌孜别克族 1
#> 3 京族 1
#> 4 仡佬族 4
#> 5 仫佬族 1
#> 6 佤族 1
#> 7 侗族 6
#> 8 俄罗斯族 1
#> 9 保安族 1
#> 10 傈僳族 2
#> # … with 46 more rows

也就是说每个民族都至少有一个代表,可以用一幅树图展示各个民族代表的比例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
library(treemapify)
df %>%
count(民族) %>%
mutate(
ishan = (民族 == "汉族") * 1
) %>%
ggplot(aes(area = n, fill = factor(ishan))) +
geom_treemap() +
geom_treemap_text(aes(label = 民族), family = slfont, size = 12, color = "#ffffff") +
scale_fill_manual(values = c("#c03728", "#919c4c")) +
labs(title = "全国人大代表的民族分布",
subtitle = "我国是一个多民族国家",
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>") +
theme_pomological(base_family = slfont,
base_size = 14) +
theme(legend.position = "none")

其中汉族人 2538 人,占总数的 85%。

学历分布

不用想就知道人大代表们的学历应该都挺高的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
df %>% 
count(性别, 学历) %>%
mutate(
学历 = fct_reorder(学历, n)
) %>%
ggplot() +
geom_chicklet(aes(x = 学历, y = n, fill = 性别)) +
theme_pomological(base_family = slfont,
base_size = 14) +
scale_fill_pomological("性别") +
scale_color_pomological("性别") +
theme(legend.position = c(0.15, 0.8)) +
labs(x = "", y = "人数",
title = "全国人大代表的学历分布",
subtitle = "每 10 名人大代表中就有 9 名持有学士及以上的学位。88.5%的代表拥\n有学士学位或以上学历。拥有硕士学位的人占最大比例(836 人),\n博士学位排名第二(584 人)。",
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>")

如果我们只关心男女比例,而不关心各种学历的人数关系,可以使用 100% 填充模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
df %>% 
count(性别, 学历) %>%
mutate(
学历 = fct_reorder(学历, n)
) %>%
ggplot() +
geom_chicklet(aes(x = 学历, y = n, fill = 性别),
position = position_identity(), alpha = 0.5) +
theme_pomological(base_family = slfont,
base_size = 14) +
scale_fill_pomological("性别") +
scale_color_pomological("性别") +
theme(legend.position = "right") +
labs(x = "", y = "人数",
title = "全国人大代表的学历分布",
subtitle = "每 10 名人大代表中就有 9 名持有学士及以上的学位。88.5%的代表拥\n有学士学位或以上学历。拥有硕士学位的人占最大比例(836 人),\n博士学位排名第二(584 人)。",
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>")

学科分布

全国人大代表的专业背景怎么样?根据中国教育部的专业分类,管理科学,哲学,文学,历史,教育,艺术,经济,法律和军事科学属于人文社会科学 ; 而科学,工程,农业和医学是自然科学。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
library(ggpol)
df %>% count(人文社科拆后专业) %>%
mutate(
人文社科拆后专业 = factor(
人文社科拆后专业,
levels = c("法学", "工学", "管理学", "教育学", "经济学",
"军事", "理学", "历史学", "农学", "未知",
"文学", "医学", "艺术", "哲学"),
labels = c("法学", "工学", "管理学", "教育学", "经济学",
"军事", "理学", "历史学", "农学", "未知",
"文学", "医学", "艺术", "哲学"))
) %>%
ggplot() +
geom_parliament(aes(seats = n, fill = 人文社科拆后专业,
color = 人文社科拆后专业)) +
coord_fixed() +
scale_fill_manual("学科",
values = rep(ggpomological:::pomological_palette, 2)[1:14],
breaks = 1:14,
labels = c("法学", "工学", "管理学", "教育学", "经济学",
"军事", "理学", "历史学", "农学", "未知",
"文学", "医学", "艺术", "哲学")) +
scale_color_manual("学科",
values = rep(ggpomological:::pomological_palette, 2)[1:14],
breaks = 1:14,
labels = c("法学", "工学", "管理学", "教育学", "经济学",
"军事", "理学", "历史学", "农学", "未知",
"文学", "医学", "艺术", "哲学")) +
guides(color = "none",
fill = guide_legend(ncol = 2)) +
theme(axis.text.x = element_blank(),
axis.text.y = element_blank()) +
theme_pomological(base_family = slfont,
base_size = 14) +
theme(axis.text = element_blank()) +
labs(title = "全国人大代表的专业背景怎么样?",
subtitle = "根据中国教育部的专业分类,管理科学,哲学,文学,历史,教育,\n艺术,经济,法律和军事科学属于人文社会科学 ; 而科学,工程,农\n业和医学是自然科学。",
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>")

党派分布

1
2
3
df %>% 
count(党派) %>%
knitr::kable(align = "c")
党派 n
中共 2172
中国民主促进会 54
九三学社 64
农工党 54
台盟 11
423
民主同盟 58
民主建国会 57
民革 44
致公党 38

可以绘制一幅冲积图来展示各个代表团的党派分布:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
library(ggalluvial)
df %>%
left_join(read_csv("delegation2.csv"), by = "Delegation") %>%
count(代表团, 党派) %>%
ggplot(aes(axis1 = 代表团, axis2 = 党派, y = n)) +
scale_x_discrete(limits = c("代表团", "党派"), expand = c(0.1, 0.05)) +
geom_stratum() +
geom_alluvium(aes(fill = 党派)) +
geom_text(stat = "stratum", label.strata = TRUE,
family = slfont, size = 3,
color = "#6f5438") +
scale_fill_pomological() +
guides(fill = "none") +
theme(axis.text.y = element_blank(),
axis.title.y = element_blank()) +
theme_pomological(base_family = slfont,
base_size = 14) +
labs(y = "",
title = "全国人大的党派和代表团分布",
subtitle = "中共的代表最多, 2172 人",
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>")

代表们籍贯的地理分布图

1
2
3
4
5
6
7
8
9
library(hchinamap)
df %>%
count(籍贯) %$%
hchinamap(name = .$籍贯, .$n,
minColor = "#fee0d2",
maxColor = "#de2d26",
title = "全国人大代表的籍贯分布",
subtitle = "来自山东的人大代表最多,为 320 人",
width = "100%", height = "500px")

还可以使用 ggplot2 + sf 绘制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
library(ggplot2)
library(sf)
mapdata <- read_sf("chinamap/中国省界.shp") %>%
left_join(df %>% count(籍贯),
by = c("NAME" = "籍贯"))
mapborder <- read_sf("chinaboundary/china_official_boundary.shp")
# 切割变量:
# 使用分位数进行切割,例如我们想分成 5 组
nclass = 5

# 计算分位数
quantiles <- mapdata %>%
pull(n) %>%
quantile(probs = seq(0, 1,
length.out = nclass + 1),
na.rm = TRUE) %>%
as.vector() %>%
round()
quantiles

# 标签
labels <- imap_chr(quantiles, function(., idx){
return(paste0(quantiles[idx], " – ",
quantiles[idx + 1]))
})
# 删除最后一个标签,要不然我们就会看到像 "484 - NA" 这样的标签:
labels <- labels[1:length(labels) - 1]
labels

# 分割
mapdata <-
mapdata %>%
mutate(
n = cut(n,
breaks = quantiles,
labels = labels,
include.lowest = TRUE)
) %>%
dplyr::filter(!is.na(n))
unique(mapdata$n)
ggplot() +
geom_sf(data = mapdata, aes(geometry = geometry,
fill = n),
size = 0.05, color = "#FFFEEA") +
geom_sf(data = mapborder, aes(geometry = geometry),
size = 0.2, color = "#F48A23") +
scale_fill_pomological() +
theme_pomological(base_family = slfont,
base_size = 14) +
theme(legend.position = c(0.2, 0.3),
legend.title = element_blank()) +
labs(title = "全国人大代表的籍贯分布",
subtitle = "来自山东的人大代表最多,为 320 人",
caption = "数据来源:Who runs China?\n<https://news.cgtn.com/event/2019/whorunschina/>") +
theme(plot.margin = grid::unit(c(0.5, 1.5, 0.5, 1), "cm"))

大概完成作业了!大家有任何疑问欢迎在知识星球和微信群里进行提问!

知识星球附件链接:https://t.zsxq.com/ayRFEim
单独购买链接:https://mianbaoduo.com/o/bread/YpiYlJ4=

# R

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×