knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE, out.width="100%") if (!require(pacman)) install.packages("pacman") p_load(drhur, here, rio, tidyverse) if (!require("dplyr")) { install.packages("dplyr") library("dplyr") } else { library("dplyr") }
现在有两份数据:
数据1:中国各省份年度不平等认知的调查数据
数据2:各省年度省内收入差距数据
如果你想研究省内收入差距对居民不平等认知的影响,你应该如何合并这两份数据呢?(提示:索引合并,以省份与年度为索引)
单数据整理
{height=400}
我们从已故政治学教授Ronald Inglehart创立的World Values Survey第七波数据(WVS7)的一个样本进行演示。
这个样本是WVS7中2%的数据,包含24个变量。
具体变量信息可通过?drhur::wvs7
查看。
数据探索指对陌生数据的数据构成、结构、形式、内容的初步了解,是数据分析的第一步,也是关键一步。
wvs7
tibble
)wvs7
nrow(wvs7) # 获取数据的行数 ncol(wvs7) # 获取数据的列数 names(wvs7) # 获取变量名/列名 str(wvs7) # 获取变量名、变量名类型、行数、列数
基于数据讨论变量特征,首先要了解如何表达数据和变量的从属关系。 包括R在内的OOP系统非常擅长多数据、多变量的协同使用和分析。 换言之,与一些常见数据分析软件不同,R可以同时加载和综合使用多个数据——只要将他们存入不同的对象即可。
OOP → 多数据分析 → 数据信息 + 变量信息
wvs7[, "country"]
wvs7$country
变量信息提取与上一节中的向量信息提取完全一致。
我们也可以通过table
命令获得变量分布,通过summary
命令获得常见变量信息。
当然,R也支持获取年龄变量的总和、平均数、中位数、最小值、最大值、方差、IQR等,这些方法我们会在下一节仔细讨论。
table(wvs7$age) summary(wvs7$age)
对于非数值型变量,我们可以通过总结表的形式获取他们的信息
table(wvs7$female) table(wvs7$marital)
对于基于factor的变量,我们还能提取他们的层级信息
levels(wvs7$religious) levels(wvs7$marital)
变量可能由于其类不同而具有不同的特征,比如定类向量就没法求平均值,因此mean
对于他们就是无意义的。
但所有变量都具有一些属性特征,比如变量的长度、类别、特征值等。
而对这些特征的提取命令也是共通的。
length(wvs7$age) #求取年份的长度(此处为行数) unique(wvs7$age) summary(wvs7$age) #获取年份的上述所有信息 class(wvs7$age) #查看年份结构:vector、matrix、array、dataframe、list typeof(wvs7$age) #查看年份元素类型
summary(wvs7$age) summary(wvs7)
如果说数据探索是从数据中看变量,那么数据梳理就是以变量为索引来了解数据。
从实用角度出发,我们这里直接介绍如何使用tidyverse
进行数据梳理。
但其实绝大部分数据梳理都是可以通过R的自带语句完成的。
我们也将自带语句对于同一任务的操作放置在“提示”单元中。
首先介绍一下tidyverse
tidyverse
组成成员都在同一个数据结构内工作,可以相互对话,共同使用。{height=300}
install.packages("tidyverse") library("tidyverse")
dplyr
包tidyverse
中专门负责数据清理的组件,贯彻一个函数做一件事的风格。
在逐一介绍dplyr
的主要命令前,我们要先说明一下“通道”(pipe)。
通道在R中可以直接使用|>
表示,在叫起dplyr
后还可以使用功能更强的%>%
。
我们后面的例子直接使用后者。
在R中,通道起到连接对于同一个对象的连续动作,相当于动作游戏中“搓”一个连续技。
{height=400}
另一方面,通道也能让每个命令排布更加明确、易读。 再举一个例子,如果我们用代码来模拟煮饺子的全过程,大体是这样:
eat_dumpling <- eat( dip( cook( fill( mix( meat, with = c(salt, soy_sauce, green_onion, ginger) ), with = wrapper ), in = boilled_water ), in = vinegar) )
使用通道后,可以写成这样
eat_dumpling <- mix(meat, with = c(salt, soy_sauce, green_onion, ginger)) %>% fill(with = wrapper) %>% cook(in = boilled_water) %>% dip(in = vinegar) %>% eat
%>%
的快捷键:
select(<data>, <var1>, <var2>, ...)
现在数据中有24个变量,有一些有意思的变量排在后面不方便看到,我们希望看到一个只有国家、年龄、教育水平和对政府信心的数据框:
select(wvs7, country, age, education, confidence_gov) # 如果我们想看到关于信心的所有变量,除了逐个列出来以外还能怎么做?
select(wvs7, country, age, education, starts_with("confidence"))
和starts_with
类似的还有ends_with
和matches
。
提示:注意第三人称变单数😝
删除变量可以通过-
实现:
select(wvs7, -(country:education))
一个select
衍生体是rename
,语法为new.name = old.name
rename(wvs7, nationality = country)
arrange(<data>,...)
比如我们好奇最年轻的人群对国家机关的信心,并对应他们的教育水平、收入水平等信息。
select(wvs7, age, confidence_gov, education, incomeLevel)
select(wvs7, age, confidence_gov, education, incomeLevel) %>% arrange(age)
那最年长的那群人呢?
select(wvs7, age, confidence_gov, education, incomeLevel) %>% arrange(desc(age)) ## 如果我们想知道最年轻又教育最高的人呢?
select(wvs7, age, confidence_gov, education, incomeLevel) %>% arrange(age, desc(education))
前面提到select
是对数据库变量的筛选,filter
则基于变量值的筛选。
延续上面的例子,如果我们好奇美国最年轻的一群人对国家机关的信心以及他们的教育水平和收入水平。
select(wvs7, age, confidence_gov, education, incomeLevel) %>% arrange(age)
select(wvs7, age, confidence_gov, education, incomeLevel, country) %>% filter(country == "United States") %>% arrange(age) select(wvs7, age, confidence_gov, education, incomeLevel, country) %>% filter(country == "United States") %>% filter(age == min(age, na.rm = TRUE))
在数据分析中,我们常常要将数据进行调整和再加工,mutate
可以帮你做到这一点。
英文中“mutate”表示“变异”,也就是说这个函数可以实现的并不是无中生有,而是改头换面。
如果我们关心教育水平对收入水平差异的影响,我们可以建立一个比例变量
mutate(wvs7, ratio_incomeEdu = incomeLevel / (education + 1)) %>% select(country, incomeLevel, education, ratio_incomeEdu) %>% arrange(desc(ratio_incomeEdu)) # 如果要把`ratio_incomeEdu`变成一个百分数,怎么办?
mutate(wvs7, ratio_incomeEdu = incomeLevel / (education + 1), ratio_incomeEdu = as.numeric(ratio_incomeEdu) %>% scales::percent()) %>% select(country, ratio_incomeEdu)
count
用来基于数据计数。
比如,可以使用count
来计算我们的数据中男性、女性各有多少人。^[这种列表在人口普查等统计数据中非常常见。]
wvs7 %>% count(female) # 如果想知道不同年龄段的男女数量怎么办呢?
wvs7 %>% count(age, female)
summarise
用来将个体数据转换成统计数据。
比如,我们想获得样本的年龄和教育水平平均数
wvs7 %>% summarise(age = mean(age, na.rm = TRUE), edu = mean(education, na.rm = TRUE))
group_by
使分组操作成为可能:
wvs7 %>% group_by(female) %>% summarise(age = mean(age, na.rm = TRUE), edu = mean(education, na.rm = TRUE))
group_by
实际是为现有数据建立群体索引,之后的所有操作都将在分组进行。
这一命令的逆操作是ungroup
。
怎么样才能填补缺失的 x
, 然后把 y
和 z
合并成一个变量呢?
df_toy <- data.frame(x = sample(c(1:2, NA, NA, NA)), y = c(1, 2, NA, NA, 5), z = c(NA, NA, 3, 4, 5)) df_toy
df_toy %>% mutate(x = coalesce(x, 0L), yz = coalesce(y, z)) # Ta-da~~~
我们上面的一系列操作都有一个共同的特点,你发现了吗?
head(wvs7)
wvs7 <- mutate(wvs7, female = as.numeric(female))
直接合并的前提基本和矩阵运算是基本一致的:只有前列数对得上后行才能进行。
分别举个例子:
wvs7_us <- filter(wvs7, country == "United States") wvs7_russia <- filter(wvs7, country == "Russia") # 创建一个美俄数据 bind_rows(wvs7_us, wvs7_russia) # 不等列行合并会发生什么?
# Try this bind_rows(tibble(x = 1:3), tibble(y = 1:4))
wvs7_conf <- select(wvs7, starts_with("confidence")) wvs7_trust <- select(wvs7, starts_with("trust")) # 创建一个信心-信任数据 bind_cols(wvs7_conf, wvs7_trust)
索引合并指的是基于共享的索引序列(可以是任何变量)合并数据。
让我们先创建两个演示数据:
如果wvs7_eq
是调查数据,wvs7_country
是人口统计数据,我们的研究需要将这两组数据合并分析个体层级和国家层级变量间的关系。
wvs7_eq <- select(wvs7, country, starts_with("equal")) %>% filter(country %in% unique(country)[1:2]) wvs7_country <- group_by(wvs7, country) %>% summarise(across(female:education, mean, na.rm = TRUE)) %>% ungroup %>% filter(country %in% unique(country)[2:3])
inner_join(wvs7_eq, wvs7_country) left_join(wvs7_eq, wvs7_country) right_join(wvs7_eq, wvs7_country) full_join(wvs7_eq, wvs7_country)
dplyr
函数;head
, tail
nrow
, ncol
, names
, str
table
, levels
length
, unique
, summary
, class
, typeof
dplyr
命令集bind_*
*_join
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.