此次分析是启发于在知乎看到的一篇文章,文章的结果中,数据分析师需要的技能没有R语言,抱着证实R语言这款工具的想法,以及希望了解当前企业对数据分析岗位的需求,开始了一次针对招聘网站的数据分析岗位招聘数据的分析与挖掘实践,避免自己所学习的方向与企业实际需求脱轨。
需要定义如下数据字典(预期希望得到的数据)
表1
| 字段 | 描述 | 用途 |
|---|---|---|
| id | 唯一标识 | 与数据库表主键功能类似,用于处理表关联 |
| title | 岗位名称 | 作为岗位的标识,与雇主和岗位描述组合成唯一标识 |
| company | 公司名称 | 作为雇主的标识 |
| salary | 平均月薪(k) | 用于平均月薪的分析 |
| city | 工作所在城市 | 用于分析地域 |
| scale | 规模 | 用于区别企业的指标 |
| phase | 融资/发展阶段 | 用于区别企业的指标 |
| experience | 职位经验要求 | 分析工作经验的影响 |
| education | 学历要求 | 分析学历的影响 |
| description | 职位描述 | 用于文本挖掘,做岗位需求技能分析 |
表2
| 字段 | 描述 | 用途 |
|---|---|---|
| id | 标识 | 与数据库表外键功能类似,用于处理表关联 |
| keyword | 关键词 | 表示一个数据分析工具或技能 |
1、初始数据加载
#readxl包的read_excel函数
library(readxl)
#数据加载
#提取所需分析的数据
CN.df <- read_excel("CN_lagou_jobdata.xlsx",1)
CN.df <- CN.df[,c("title","salary","experience","education","campany","scale","scale2","description","phase","city")]
#观察数据,分类变量不是因子的格式,且存在不需要的字符
str(CN.df)
## Classes 'tbl_df', 'tbl' and 'data.frame': 449 obs. of 10 variables:
## $ title : chr "数据分析师" "数据分析师" "数据分析" "数据分析师" ...
## $ salary : chr "10k-20k" "12k-20k" "15k-30k" "15k-25k" ...
## $ experience : chr "经验5-10年 /" "经验不限 /" "经验1-3年 /" "经验1-3年 /" ...
## $ education : chr "本科及以上 /" "本科及以上 /" "本科及以上 /" "本科及以上 /" ...
## $ campany : chr "\n Gridsum 国双\n" "\n 酷家乐\n" "\n 快看漫画\n" "\n 作业盒子\n" ...
## $ scale : chr "\n 500-2000人\n 规模\n" "\n GGV、IDG、经纬、线性资本、云启资本、赫斯特资本(B轮),IDG(A轮)\n 投资机构\n" "\n Coatue Management、华人文化产业投资基金、襄禾资本等(D轮及以上)\n 投资机构\n" "\n 联想之星(天使轮),好未来、刘强东、奶茶妹妹、联想之星(A轮),贝塔斯曼领投(B轮)\n 投资机构\n" ...
## $ scale2 : chr "\n \n http://www.gridsum.com\n 公司主页\n" "\n 500-2000人\n 规模\n" "\n 150-500人\n 规模\n" "\n 500-2000人\n 规模\n" ...
## $ description: chr "\n 岗位职责:\n利用国双自有的一系列监测工具采集、分析客户网站、APP、媒体投放所生成的大数据。\n2. 学会并"| __truncated__ "\n 岗位描述:\n1、负责酷家乐业务数据分析,包括报表设计、数据建模等;针对产品和用户,有能力在海量数据中进"| __truncated__ "\n 工作职责:\n1、负责运营业务的产品以及运营数据指标的搭建\n2、监控运营相关数据,能从数据异动中发现问题"| __truncated__ "\n 岗位职责:\n1、负责分析和解释产品试验,市场活动等的结果,为产品改进,推广试验等提供数据支持;\n2、负"| __truncated__ ...
## $ phase : chr "上市公司\n" "D轮及以上\n" "D轮及以上\n" "C轮\n" ...
## $ city : chr "/北京 /" "/杭州 /" "/北京 /" "/北京 /" ...
2、识别缺失值
#识别缺失值
#VIM包的aggr函数来识别
library(VIM)
aggr(CN.df,prop=TRUE,numbers=TRUE)
结果显示,我们关心的数据不存在什么缺失值,这对于数据处理来说是极好的信息
3、处理数据
#zoo包的index函数
library(zoo)
#定义数据清洗函数
cleaning <- function(my.data){
#删除重复值,结果显示还有443行数据
my.data <- my.data[!duplicated(my.data[c("title","campany","description")]),]
#计算平均月薪
min_salary <- as.numeric(sub("([0-9]*).*","\\1",my.data$salary))
max_salary <- as.numeric(sub(".*-([0-9]*).*","\\1",my.data$salary))
my.data$avg_salary <- (max_salary + min_salary)/2
#清理字符串中的不需要的字符
#并将需要分析的字符变量转化为因子,并对部分因子重新编码
my.data$city <- factor(gsub("[/ ]*","",my.data$city))
my.data$experience <- gsub("经验|[/ ]*","",my.data$experience)
my.data$experience[my.data$experience %in% c("不限","应届毕业生")] <- "1年以下"
my.data$experience <- factor(my.data$experience,
levels=c("1年以下","1-3年","3-5年","5-10年","10年以上"))
#这里的学历:“大专”,“本科”,“硕士”都表明是要求该学历“及以上”
my.data$education <- gsub("学历|及以上|[/ ]*","",my.data$education)
my.data$education[my.data$education=="不限"] <- "大专"
my.data$education <- factor(my.data$education,levels=c("大专","本科","硕士"))
my.data$phase <- factor(gsub("[\n]*","",my.data$phase),levels =
c("不需要融资","未融资","天使轮","A轮",
"B轮","C轮","D轮及以上","上市公司"))
my.data$campany <- gsub("[\n| ]*","",my.data$campany)
my.data$scale <- factor(gsub(".*(少于15人|15-50人|50-150人|150-500人|500-2000人|2000人以上).*",
"\\1",paste(my.data$scale,my.data$scale2)),
levels =c("少于15人","15-50人","50-150人",
"150-500人","500-2000人","2000人以上"))
my.data$id <- index(my.data)
my.data <- droplevels(subset(my.data,select=-scale2))
return(my.data)
}
#清洗数据,得到清洗后的数据
CN.clean <- cleaning(CN.df)
str(CN.clean)
## Classes 'tbl_df', 'tbl' and 'data.frame': 442 obs. of 11 variables:
## $ title : chr "数据分析师" "数据分析师" "数据分析" "数据分析师" ...
## $ salary : chr "10k-20k" "12k-20k" "15k-30k" "15k-25k" ...
## $ experience : Factor w/ 4 levels "1年以下","1-3年",..: 4 1 2 2 3 2 3 3 3 3 ...
## $ education : Factor w/ 3 levels "大专","本科",..: 2 2 2 2 2 2 2 2 2 2 ...
## $ campany : chr "Gridsum国双" "酷家乐" "快看漫画" "作业盒子" ...
## $ scale : Factor w/ 5 levels "15-50人","50-150人",..: 4 4 3 4 3 3 5 4 4 5 ...
## $ description: chr "\n 岗位职责:\n利用国双自有的一系列监测工具采集、分析客户网站、APP、媒体投放所生成的大数据。\n2. 学会并"| __truncated__ "\n 岗位描述:\n1、负责酷家乐业务数据分析,包括报表设计、数据建模等;针对产品和用户,有能力在海量数据中进"| __truncated__ "\n 工作职责:\n1、负责运营业务的产品以及运营数据指标的搭建\n2、监控运营相关数据,能从数据异动中发现问题"| __truncated__ "\n 岗位职责:\n1、负责分析和解释产品试验,市场活动等的结果,为产品改进,推广试验等提供数据支持;\n2、负"| __truncated__ ...
## $ phase : Factor w/ 8 levels "不需要融资","未融资",..: 8 7 7 6 6 5 8 7 6 8 ...
## $ city : Factor w/ 23 levels "北京","常州",..: 1 10 1 1 10 15 1 1 10 1 ...
## $ avg_salary : num 15 16 22.5 20 15 15 15 15 15 20 ...
## $ id : int 1 2 3 4 5 6 7 8 9 10 ...
4、文本挖掘 采用jiebaR分词器进行文本挖掘
声明: 笔者测试过jiebaR的关键词分词器,测试的结果发现SQL,Python等词在jiebaR词典中的IDF值均为11.7392,但“R”这个字符无论如何(即使自定义了用户字典,或者在idf字典中添加R的idf值)都无法被分词器识别为关键词,猜测可能是默认R的词性标注或者算法实现方法的原因。但因为R是数据分析师的重要工具,识别不出来是不可容忍的,因此要求出路。
为词频,IDF为逆文档率(词的权重),因此TF-IDF=TF*IDF为衡量是否关键词的指标,若控制IDF,则TF-IDF的值与TF值成正比关系,简单来说TF值可以代替TF-IDF值。 由于此次分析的是数据分析师的工具和技能,因此只考虑SQL,PYTHON,R,SAS等常用且类似的词的分析,又因为该类词在jiebaR分词器识别出来的IDF值均是同级别的(即使可能存在有差异也在此假设其等值),因此这部分词汇的关键指标的衡量可以简化为出现的词频,即TF值,这个可以通过jiebaR的默认分词器来处理可得。
挖掘思路:
1、工具型技能关键词挖掘->分析岗位技能需求
2、非工具型的技能或理论知识关键词挖掘->分析岗位非工具型技能需求
library(jiebaR)
library(jiebaRD)
library(zoo)
library(plyr)
source("myfun.R")
#提取技能型关键词
#采用默认jiebaR分词器
engine <- worker(user = "user_dict.txt")
#分词,并删除无关的词汇
word.lis <- lapply(CN.clean$description, function(x){
v <- gsub("[\u4e00-\u9fa5|0-9|\\.|\\-]","",segment(x,engine))
v <- v[v!=""]
return(v)
})
#将所有分出来的词转化为大写,消除大小写差异
segWords <- toupper(unlist(word.lis))
stopwords <- toupper(readLines("stopwords.txt"))
#过滤停词,由于文本可能会存在其他高频的词汇,把不需要的词去除,如(and,of…)
#此处确保我要得到的前20个关键技能是正确的数据分析技能
segWords<-filter_segment(segWords,stopwords)
#形成词频表(数据框格式),获取前15个技能关键词
top15.df <- top.freq(segWords,topn = 15)
#生成有id和keyword构建的数据框,id对应cleandata数据集的id(即数据字典表1和表2的关系)
id <- NULL
keyword <- NULL
for (i in index(word.lis)) {
id <- c(id,rep(i,length(word.lis[[i]])))
keyword <- c(keyword,word.lis[[i]])
}
keyword.df <- data.frame("id"=id,"keyword"=toupper(keyword))
keyword.df <- droplevels(keyword.df[keyword.df$keyword %in% top15.df$x,])
str(keyword.df)
## 'data.frame': 1791 obs. of 2 variables:
## $ id : int 1 1 1 2 2 2 2 4 4 4 ...
## $ keyword: Factor w/ 15 levels "BI","EXCEL","HADOOP",..: 2 9 15 2 15 3 10 15 10 11 ...
#合并两个数据集(表之间的内连接,类似sql语句的inner jion)
merge.df <- merge(CN.clean,keyword.df,by="id")
str(merge.df)
## 'data.frame': 1791 obs. of 12 variables:
## $ id : int 1 1 1 2 2 2 2 4 4 4 ...
## $ title : chr "数据分析师" "数据分析师" "数据分析师" "数据分析师" ...
## $ salary : chr "10k-20k" "10k-20k" "10k-20k" "12k-20k" ...
## $ experience : Factor w/ 4 levels "1年以下","1-3年",..: 4 4 4 1 1 1 1 2 2 2 ...
## $ education : Factor w/ 3 levels "大专","本科",..: 2 2 2 2 2 2 2 2 2 2 ...
## $ campany : chr "Gridsum国双" "Gridsum国双" "Gridsum国双" "酷家乐" ...
## $ scale : Factor w/ 5 levels "15-50人","50-150人",..: 4 4 4 4 4 4 4 4 4 4 ...
## $ description: chr "\n 岗位职责:\n利用国双自有的一系列监测工具采集、分析客户网站、APP、媒体投放所生成的大数据。\n2. 学会并"| __truncated__ "\n 岗位职责:\n利用国双自有的一系列监测工具采集、分析客户网站、APP、媒体投放所生成的大数据。\n2. 学会并"| __truncated__ "\n 岗位职责:\n利用国双自有的一系列监测工具采集、分析客户网站、APP、媒体投放所生成的大数据。\n2. 学会并"| __truncated__ "\n 岗位描述:\n1、负责酷家乐业务数据分析,包括报表设计、数据建模等;针对产品和用户,有能力在海量数据中进"| __truncated__ ...
## $ phase : Factor w/ 8 levels "不需要融资","未融资",..: 8 8 8 7 7 7 7 6 6 6 ...
## $ city : Factor w/ 23 levels "北京","常州",..: 1 1 1 10 10 10 10 1 1 1 ...
## $ avg_salary : num 15 15 15 16 16 16 16 20 20 20 ...
## $ keyword : Factor w/ 15 levels "BI","EXCEL","HADOOP",..: 2 9 15 2 15 3 10 15 10 11 ...
#提取非技能型关键词
keys <- worker(type = "keywords",user = "user_dict.txt",topn = 20,stop_word = "stopkw.txt")
keyword.lis <- lapply(CN.clean$description, function(x){
v <- gsub("[a-zA-Z|0-9|\\.|\\-]","",keywords(x,keys))
v <- v[v!=""]
return(v)
})
keyword.lis <- unlist(keyword.lis)
#形成词频表
not.tool.keyword <- top.freq(keyword.lis)
str(not.tool.keyword)
## 'data.frame': 1497 obs. of 2 variables:
## $ x : Factor w/ 1497 levels "阿里","爱奇艺",..: 1243 990 1349 543 96 1298 742 439 1085 1097 ...
## $ freq: int 236 157 132 119 110 108 87 85 82 76 ...
library(ggplot2)
library(plyr)
library(wordcloud2)
attach(CN.clean)
## The following object is masked _by_ .GlobalEnv:
##
## id
source("myfun.R")
#观察数据的总体描述统计信息
summary(CN.clean[c("city","phase","scale","education","experience","avg_salary")])
## city phase scale education experience
## 北京 :222 不需要融资:94 15-50人 : 15 大专: 33 1年以下: 34
## 杭州 : 48 上市公司 :88 50-150人 : 36 本科:389 1-3年 :156
## 上海 : 48 D轮及以上 :80 150-500人 :104 硕士: 20 3-5年 :206
## 广州 : 38 C轮 :73 500-2000人:116 5-10年 : 46
## 深圳 : 38 B轮 :49 2000人以上:171
## 长沙 : 11 A轮 :42
## (Other): 37 (Other) :16
## avg_salary
## Min. : 2.50
## 1st Qu.:12.50
## Median :17.50
## Mean :18.22
## 3rd Qu.:22.50
## Max. :75.00
##
#创建ggplot绘图对象
p.cn <- ggplot(CN.clean)+my.ggplot.theme()
#问题1:不同地区,数据分析岗位的需求分布以及对应的薪资分布
city.table <- data.frame(prop.table(table(reorder_size(city,TRUE))))
names(city.table)[1] <- "city"
p1 <- ggplot(city.table,aes(x=city,y=Freq)) + my.ggplot.theme()+
geom_bar(fill="turquoise3",stat = "identity") +
labs(x="城市",y="不同城市需求占总量的比率",
title="“北京,上海,杭州,深圳,广州”占据了近90%的需求量,\n是数据分析师的首选") +
scale_y_continuous(labels = scales::percent)
group_diff <- diff(range(avg_salary))/20
p2 <- p.cn + geom_histogram(aes(x=avg_salary,y=..density..),
binwidth = group_diff,fill="turquoise3",color="white")+
stat_density(geom = "line",position = "identity",
aes(x=avg_salary),color="brown1")+
labs(x="月薪(K/月)",
title="数据分析师平均月薪为18.22k,月薪的分布主要集中在10k~25k之间,\n拿到10k以上薪资的机会比较大")
multiplot(p1,p2,cols = 1)
CN.clean$type <- NA
CN.clean$type[CN.clean$city %in% top.freq(city,5)$x] <- "top5"
CN.clean$type[is.na(CN.clean$type)] <- "other"
CN.clean$type <- factor(CN.clean$type,levels=c("top5","other"))
p.cn + geom_boxplot(aes(x=city,y=avg_salary,fill=CN.clean$type))+
labs(x="城市",y="月薪(K/月)",
title="需求量最多的5个城市的平均薪资均处于全国较高的水平,\n(苏州是一个特例,需求量少,薪资高)",fill="需求量排名")+
theme(axis.text.x = element_text(angle = 30,hjust = 1))
#问题2:不同经验,数据分析岗位的需求分布以及对应的薪资分布
exp.table <- prop.table(table(experience))
exp.table <- as.data.frame(exp.table)
p3 <- ggplot(exp.table,aes(x=experience,y=Freq)) + my.ggplot.theme()+
geom_bar(fill="turquoise3",stat = "identity") +
labs(x="工作经验",y="不同工作经验需求占总量的比率",
title="企业需要更有经验的分析师,主要需求\n集中在1-3年和3-5年经验") +
scale_y_continuous(labels = scales::percent)
p4 <- p.cn + geom_boxplot(aes(x=experience,y=avg_salary),fill="turquoise3")+
labs(x="工作经验",y="平均月薪(K/月)",
title="随着工作经验的增加,数据分析师的\n月薪有非常可观的增长")
multiplot(p3,p4,cols = 2)
#问题3:不同学历,数据分析岗位的需求分布以及对应的薪资分布
edu.table <- prop.table(table(education))
edu.table <- as.data.frame(edu.table)
p5 <- ggplot(edu.table,aes(x=education,y=Freq)) + my.ggplot.theme()+
geom_bar(fill="turquoise3",stat = "identity") +
labs(x="学历",y="不同学历需求占总量的比率",title="超过90%的岗位需要本科及以上的学历") +
scale_y_continuous(labels = scales::percent)
p6 <- p.cn + geom_boxplot(aes(x=education,y=avg_salary),fill="turquoise3")+
labs(x="学历",y="月薪(K/月)",
title="学历与薪资水平增长的关系不明显")
multiplot(p5,p6,cols = 2)
#问题4:不同企业规模,数据分析岗位的各项需求分布及薪资分布
scale.table <- data.frame(prop.table(table(scale)))
p7 <- ggplot(scale.table,aes(x=scale,y=Freq)) + my.ggplot.theme()+
geom_bar(fill="turquoise3",stat = "identity") +
labs(x="企业规模",y="不同企业规模需求占总量的比率",title="接近90%的需求量集中在150人以上\n规模的企业")+
theme(axis.text.x = element_text(angle = 30,hjust = 1))+
scale_y_continuous(labels = scales::percent)
p8 <- p.cn + geom_boxplot(aes(x=scale,y=avg_salary),fill="turquoise3")+
labs(x="企业规模",y="月薪(K/月)",
title="150人以下规模且需快速发展的企业\n愿意给出更高的薪酬")+
theme(axis.text.x = element_text(angle = 30,hjust = 1))
multiplot(p7,p8,cols = 2)
#企业规模与工作经验要求分析
sc.exp <- data.frame(prop.table(table(scale,experience),1))
ggplot(data=sc.exp,aes(x=scale,y=Freq,fill=experience)) + my.ggplot.theme()+
geom_bar(stat = "identity")+
labs(x="企业规模",y="需求比例",fill="工作经验",
title="50~150人规模的企业对分析师的工作经验要求最高")+
geom_text(aes(label=paste(round(sc.exp$Freq,3)*100,'%',sep = '')),colour = "white",
position=position_stack(.5), vjust=00)+
scale_y_continuous(labels = scales::percent)
#问题5:探索数据分析岗位对工具型技能的需求
#工具型技能分布
key.df <- data.frame(table(reorder_size(merge.df$keyword,TRUE)))
key.df$Freq <- key.df$Freq/length(CN.clean$id)
ggplot(key.df)+my.ggplot.theme() +
geom_bar(aes(x=Var1,y=Freq),fill = "turquoise3",stat = "identity") +
labs(x="工具型技能",y="不同技能需求占总岗位需求量的比率",
title="SQL,R,Python,Excel是数据分析师的必备技能,超过78%的岗位都要求掌握SQL,
\nR语言的需求量居于第二") +
theme(axis.text.x = element_text(angle = 30,hjust = 1))+
geom_text(aes(x=Var1,y=Freq,label=paste(round(key.df$Freq,3)*100,'%',sep = '')),vjust=-0.2)+
scale_y_continuous(labels = scales::percent)
#工具型技能与工作经验的分析
key.exp <- data.frame(prop.table(table(merge.df$keyword,merge.df$experience),2))
names(key.exp)[1:2] <- c("kw","exp")
ggplot(key.exp)+my.ggplot.theme()+
geom_bar(aes(x=kw,y=Freq,fill=exp),stat = "identity")+
labs(x="工具型技能",y="比率",fill="工作经验",
title="工作经验要求越高对BI、EXCEL等office工具的需求越少,
\n对数据库、Hadoop和Hive等大数据工具的需求越高")+
facet_grid(exp~.)+
theme(axis.text.x = element_text(angle = 30,hjust = 1))+
geom_text(aes(x=kw,y=Freq,label=paste(round(key.exp$Freq,3)*100,'%',sep = '')),vjust=0,size=3)+
scale_y_continuous(labels = scales::percent)
#工具型技能与薪资的分析
merge.df$type <- NA
merge.df$type[merge.df$keyword %in% top.freq(merge.df$keyword,5)$x] <- "top5"
merge.df$type[is.na(merge.df$type)] <- "other"
merge.df$type <- factor(merge.df$type,levels=c("top5","other"))
ggplot(merge.df) + my.ggplot.theme()+
geom_boxplot(aes(x=keyword,y=avg_salary,fill=merge.df$type))+
labs(x="工具型技能",y="月薪分布(K/月)",
title="掌握Spark,Hadoop,Hive,R,Python等工具可以获得更高的薪资",fill="需求量排名")+
theme(axis.text.x = element_text(angle = 30,hjust = 1))
#问题6:探索非工具型关键词的需求
#生成词云
wordcloud2(not.tool.keyword, size = 0.9, fontFamily = "微软雅黑")
detach(CN.clean)
本次分析并没有按照分析报告的方式来呈现,文章中以个人的整个分析过程来撰写,希望能够与各位朋友一起交流学习,如果你不同意我文章中的观点,欢迎指正交流。
文章中我附上了我的数据集以及分析的代码链接,有兴趣的朋友可以重复我的过程,甚至做更加深入有趣的分析,如果有新的发现和观点,希望也能让我知道,向你们学习。
代码中还有部分函数是本人自己写的,存储在myfun.R文件中,代码如下:
#自定义ggplot2主题函数
my.ggplot.theme <- function(..., bg='white'){
require(grid)
theme_classic(...) +
theme(rect=element_rect(fill=bg),
plot.title = element_text(hjust = 0.5),
text = element_text(family="STHeiti"),
panel.background=element_rect(fill='transparent', color='#333333'),
axis.line = element_line(colour = "#333333",size = 0.25),
legend.key=element_rect(fill='transparent', color='transparent'),
panel.border=element_rect(fill='transparent', color='transparent'),
panel.grid=element_line(colour = 'grey95'),
panel.grid.major = element_line(colour = "grey92",size = 0.25),
panel.grid.minor = element_line(colour = "grey92",size = 0.1))
}
#构建排序函数
reorder_size <- function(x,decreasing=TRUE) {
factor(x, levels = names(sort(table(x),decreasing = decreasing)))
}
#返回一个频数表的数据框
top.freq <- function(x,topn=0){
require(plyr)
top.df <- count(x)
top.df <- top.df[order(top.df$freq,decreasing = TRUE),]
if(topn > 0) return(top.df[1:topn,])
else return(top.df)
}
#ggplot2 多图显示函数
multiplot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
library(grid)
# Make a list from the ... arguments and plotlist
plots <- c(list(...), plotlist)
numPlots = length(plots)
# If layout is NULL, then use 'cols' to determine layout
if (is.null(layout)) {
# Make the panel
# ncol: Number of columns of plots
# nrow: Number of rows needed, calculated from # of cols
layout <- matrix(seq(1, cols * ceiling(numPlots/cols)),
ncol = cols, nrow = ceiling(numPlots/cols))
}
if (numPlots==1) {
print(plots[[1]])
} else {
# Set up the page
grid.newpage()
pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
# Make each plot, in the correct location
for (i in 1:numPlots) {
# Get the i,j matrix positions of the regions that contain this subplot
matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))
print(plots[[i]], vp = viewport(layout.pos.row = matchidx$row,
layout.pos.col = matchidx$col))
}
}
}