人口金字塔:从数据清洗到制图(STATA)

目标

制作2021年人口金字塔

  • 清洗文本型数据:usubstr和usubinstr
  • 转换数据类型:encode
  • 调整因子变量的值和标签的排序:elabel recode
  • 水平条形图:twoway bar, horizontal

1、获取原始数据

国家统计局网站获取按年龄分性别的人口抽样调查数据。

age y2021
男性人口数(人口抽样调查)(人) 763842
0-4岁男性人口数(人口抽样调查)(人) 38288
5-9岁男性人口数(人口抽样调查)(人) 50970
10-14岁男性人口数(人口抽样调查)(人) 49345
15-19岁男性人口数(人口抽样调查)(人) 42677
20-24岁男性人口数(人口抽样调查)(人) 41020
25-29岁男性人口数(人口抽样调查)(人) 48189
30-34岁男性人口数(人口抽样调查)(人) 66101
35-39岁男性人口数(人口抽样调查)(人) 56115
40-44岁男性人口数(人口抽样调查)(人) 50206
45-49岁男性人口数(人口抽样调查)(人) 58320
50-54岁男性人口数(人口抽样调查)(人) 65216
55-59岁男性人口数(人口抽样调查)(人) 61237
60-64岁男性人口数(人口抽样调查)(人) 35357
65-69岁男性人口数(人口抽样调查)(人) 39951
70-74岁男性人口数(人口抽样调查)(人) 27247
75-79岁男性人口数(人口抽样调查)(人) 16457
80-84岁男性人口数(人口抽样调查)(人) 10175
85-89岁男性人口数(人口抽样调查)(人) 5066
90-94岁男性人口数(人口抽样调查)(人) 1643
95岁以上男性人口数(人口抽样调查)(人) 263
女性人口数(人口抽样调查)(人) 730212
0-4岁女性人口数(人口抽样调查)(人) 34690
5-9岁女性人口数(人口抽样调查)(人) 45124
10-14岁女性人口数(人口抽样调查)(人) 42959
15-19岁女性人口数(人口抽样调查)(人) 36737
20-24岁女性人口数(人口抽样调查)(人) 36236
25-29岁女性人口数(人口抽样调查)(人) 43540
30-34岁女性人口数(人口抽样调查)(人) 61955
35-39岁女性人口数(人口抽样调查)(人) 53057
40-44岁女性人口数(人口抽样调查)(人) 47817
45-49岁女性人口数(人口抽样调查)(人) 56225
50-54岁女性人口数(人口抽样调查)(人) 64103
55-59岁女性人口数(人口抽样调查)(人) 60752
60-64岁女性人口数(人口抽样调查)(人) 35399
65-69岁女性人口数(人口抽样调查)(人) 41394
70-74岁女性人口数(人口抽样调查)(人) 28962
75-79岁女性人口数(人口抽样调查)(人) 18429
80-84岁女性人口数(人口抽样调查)(人) 12368
85-89岁女性人口数(人口抽样调查)(人) 7252
90-94岁女性人口数(人口抽样调查)(人) 2606
95岁以上女性人口数(人口抽样调查)(人) 608

2、数据整理

2.1 字符串变量清洗

2.1.1 利用substr系列函数清洗

/* 先用split命令对字符串变量进行拆分 */
split age, parse("性") gen(agegrp)
list
drop agegrp2
/* 利用usubstr()函数对中文字符提取 */
gen gender1=usubstr(agegrp1, -1,1)
/* 利用subinstr()函数对中文字符替换*/
replace agegrp1=subinstr(agegrp1,gender1,"", .)
list

小笔记


split *strvar* [if] [in] [, *options*]

常用选项为parse(parse_strings),设定拆分的分隔符,默认为空格。本例中自行设定为一个统一的字符“性”。

udsubstr(s,n1,n2)

提取字符串s中从第n1个字符开始,n2列的子字符串。
例如:

  • udsubstr("médiane",2,3) = "édi"
  • udsubstr("中值",1,1) = ""
  • udsubstr("中值",1,2) = "中"

substr()不能处理中文字符,需要用替代函数usubstr()或udsubstr()。

usubstr(s,n1,n2)

提取字符串s中从第n1个字符开始,长度为n2个的子字符串。

  • usubstr("中值",1,1) = "中"
  • usubstr("中值",1,2) = "中值"

subinstr(s1,s2,s3,n)

字符串s1中出现的前n个子字符串s2,替换成s3。如果n=.,那么所有的子字符串都替换。
例如:

  • subinstr("this is the day","is","X",1) = "thX is the day"
  • subinstr("this is the hour","is","X",2) = "thX X the hour"
  • subinstr("this is this","is","X",.) = "thX X thX"

如果处理中文字符,最好用替代变量usubinstr()
本例中,由于是变量gender1是从原变量agegrp1中提取的子符串生成,所以在函数subinstr()中可作为原变量的子字符串,这样函数中并不直接出现中文,所以也可以用函数subinstr()来清洗。

2.1.2 利用ustrregex系列函数清洗

/*利用正规表达式将字符串拆成几个部分,分别提取变量 */

gen agegrp2=ustrregexs(1) if ustrregexm(age, "(.*)(男性|女性)(.*)")
gen gender2=ustrregexs(2) if ustrregexm(age, "(.*)(男性|女性)(.*)")


小笔记

ustrregex系列函数

  • u代表unicode
  • str代表string
  • reg代表regular,ex代表expression,regex代表正则表达式
  • m代表match匹配
  • ra代表replace替换
  • s代表subexpression提取子字符串

ustrregexm(s,re[,noc]) 匹配

  • s代表字符串,也可以是相应的变量
  • re代表正则表达式
  • noc,默认区分大小写。如果noc不为0,则不区分大小写。
  • 字符串s中是否存在与正则表达式re相匹配的子字符串。如果有,返回1; 没有,返回0。
  • ustrregexm("12345", "([0-9]){5}") = 1
  • ustrregexm("de TRÈS près", "rès") = 1
  • ustrregexm("de TRÈS près", "Rès") = 0
  • ustrregexm("de TRÈS près", "Rès", 1) = 1

ustrregexra(s1,re,s2[,noc]) 替换

  • ustrregexra("très près", "rès", "X") = "tX pX"
  • ustrregexra("TRÈS près", "Rès", "X") = "TRÈS près"
  • ustrregexra("TRÈS près", "Rès", "X", 1) = "TX pX"

ustrregexs(n) 提取

  • 在ustrregexm()之后,配合其使用,提取子字符串。
  • n为非负整数,代表ustrregexm(s,re)中第n个子正则表达式对应的子字符串。
  • 若n为0,则代表ustrregexm()中正则表达式对应的所有子字符串。
  • 本例中将变量age的用正则表达式表达时拆成了三个部分,分别用()隔开,仅提取第1和第2部分。

3、数据整理:因子变量

3.1 encode命令转换变量类型

'twoway bar yvar xvar'命令是将数值型(y,x)数据展示成条形图。
本例中,变量agegrp1是文本型,需要转换成带标签的数值型变量,即因子变量。

/*数据转换之前,先删除总人口数据,只保留分年龄段数据*/
drop if agegrp1==""
/*包含非数字的文本型转换成因子变量*/
encode agegrp1, gen(agegrp)
/*显示agegrp的值和标签*/
label list agegrp

注意,5-9岁的位置不对,应该是2而不是10。
目前,还没有找到比较好的对标签重新排序的办法,只能采取最笨的办法重新定义。

label define agegrp 1 "0-4岁" 3 "10-14岁" 4 "15-19岁" 5 "20-24岁" ///
6 "25-29岁" 7 "30-34岁" 8 "35-39岁" 9 "40-44岁" 10 "45-49岁" ///
2 "5-9岁" 11 "50-54岁" 12 "55-59岁" 13 "60-64岁" 14 "65-69岁" ///
15 "70-74岁" 16 "75-79岁" 17 "80-84岁" 18 "85-89岁" 19 "90-94岁" ///
20 "95岁以上", replace

label list agegrp

一个可能更省事的解决办法: 生成保存label的do文件,在文件里手工改。

/*碰到r(603)错误:file mylabel.do could not be opened的解决办法*/
mkdir C:/results
cd C:/results

/*将值和标签保存到一个do文件*/
label save agegrp using mylabel.do

mylabel.do文件的内容如下:

label define agegrp 1 `"0-4岁"', modify
label define agegrp 2 `"5-9岁"', modify
label define agegrp 3 `"10-14岁"', modify
label define agegrp 4 `"15-19岁"', modify
label define agegrp 5 `"20-24岁"', modify
label define agegrp 6 `"25-29岁"', modify
label define agegrp 7 `"30-34岁"', modify
label define agegrp 8 `"35-39岁"', modify
label define agegrp 9 `"40-44岁"', modify
label define agegrp 10 `"45-49岁"', modify
label define agegrp 11 `"50-54岁"', modify
label define agegrp 12 `"55-59岁"', modify
label define agegrp 13 `"60-64岁"', modify
label define agegrp 14 `"65-69岁"', modify
label define agegrp 15 `"70-74岁"', modify
label define agegrp 16 `"75-79岁"', modify
label define agegrp 17 `"80-84岁"', modify
label define agegrp 18 `"85-89岁"', modify
label define agegrp 19 `"90-94岁"', modify
label define agegrp 20 `"95岁以上"', modify

3.2 elabel命令修改变量标签

命令elabel是外部命令,需要安装net install elabel.pkg
它实际上包含一组修改因子变量的值和标签的命令。

常规命令recode,只能修改值,不能修改标签,即标签不随值变动。
配合命令elabel,就能实现标签随值的修改一起变动。

elabel recode agegrp  (2=3) (3=4) (4=5) (5=6) (6=7) (7=8) (8=9) (9=10) (10=2)
label list agegrp

4、作图

4.1 简图

gen pop=y2021/1000
replace pop=-pop if gender1=="男"
twoway bar pop agegrp if gender1=="男", horizontal  ///
    || bar pop agegrp if gender1=="女", horizontal

4.2 标准图

#delimit ;
twoway bar pop agegrp if gender1=="男", horizontal || 
       bar pop agegrp if gender1=="女", horizontal   
       title("2021年中国人口金字塔(抽样调查数据)") 
       note("数据来源:国家统计局")  
       xtitle("人口数(千人)") 
       xlabel(-80 "80" -60 "60" -40 "40" -20 "20" 0(20)80)
       ytitle("年龄组") ylabel(1(1)20,valuelabel angle(0))
       legend(order(1 "男" 2 "女"))
;
#delimit cr

Graph2.png

小笔记

命令换行的三种方式:

  • 在行尾///
  • 行尾/*,行首*/
  • 段首#delimit ; ,段尾; #delimit cr

Y轴,ylabel()默认显示yvar的值,选项valuelabel则可显示标签。

4.3 无Y轴图

gen zero=0
#delimit ;
twoway bar pop agegrp if gender1=="男", horizontal || 
       bar pop agegrp if gender1=="女", horizontal   ||
       scatter agegrp zero, mlabel(agegrp) mlabcolor(black) msymbol(none)
       title("2021年中国人口金字塔(抽样调查数据)") 
       note("数据来源:国家统计局")  
       xtitle("人口数(千人)") 
       xlabel(-80 "80" -60 "60" -40 "40" -20 "20" 0(20)80)
       ytitle("") ylabel(none)  yscale(noline)
       legend(off) text(18 -50 "男") text(18 50 "女")
;
#delimit cr

Graph1.png

小笔记

  • 消除Y轴: ytitle("") ylabel(none) yscale(noline)
  • 构建一个垂直散点图,将年龄段刻在图正中:gen zero=0scatter agegrp zero
  • 添加年龄段标签,并消除点的形状和颜色:mlabel(agegrp) mlabcolor(black) msymbol(none)

5、另一种数据结构和画法:graph hbar

  • 命令twoway bar要求数据是二维的。
  • 命令graph hbar要求数据是一维的。
  • 选项over()将其它维度的分形放入同一图中。
  • 选项by()将其它维度的分形分别作图。

5.1 整理数据

目标是生成两个性别变量的按年份分年龄段的人口数据。

year agegrp pop_male pop_female
/*只保留所需的变量*/
keep y2021-y2013 gender1 agegrp 
/*第一次变换,围绕历年数据,由宽表变成长表*/
/*首先,生成唯一标识的索引*/
gen no=_n  
/*提取原变量y2021-y2023中的y, 保存历年人口数据*/
/*提取原变量y2021-y2023中的年份,另存在新变量year*/
reshape long y, i(no) j(year)
list
drop no
rename y pop

/*第二次变换,围绕性别,由长表变宽表*/
/*由于gender1是文本型,首先要转成数据型*/
encode gender1, gen(gender)
drop gender1
/*重新排序,方便生成reshape命令需要索引*/
sort gender year agegrp
/*不同的性别,分别生成按年份分年龄段的唯一索引*/
gen no2=_n if gender==1
replace no2=_n-160 if gender==2
reshape wide pop, i(no2) j(gender)
list
rename pop1 pop_female
rename pop2 pop_male
list

5.2 作图

replace pop_male=-pop_male
replace pop_male=pop_male/1000
replace pop_female=pop_female/1000

#delimit ;
graph hbar (asis) pop_male pop_female if year==2021, 
            over(agegrp, descending gap(0)) 
            stack
            legend(order(1 "男" 2 "女"))
            title("2021年中国人口金字塔(抽样调查数据)") 
            note("数据来源:国家统计局")  
            ylabel(-80 "80" -60 "60" -40 "40" -20 "20" 0(20)80)
            ytitle("人口数(千人)")

;
#delimit cr

小笔记

  • graph hbar var 默认的统计方法是(mean), (asis)保持原数据不变。
  • over()中,descending降序排列,gap()指定条形之间的间隔。
  • stack堆叠,让不同维度的分形堆叠在一起。如果不设置,则分形是错开的。
  • graph hbar图中的x轴实际上是它的y轴,所以人口数值和标签设置使用ylabel。
Graph3.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容