本文概述
本教程的目的是向你展示如何通过使用R进行网页抓取来收集有关H1B签证的数据。接下来, 你还将学习如何解析JSON对象, 以及如何存储和处理数据, 以便可以对H1B申请的大数据集进行基本的探索性数据分析(EDA)。
也许你可以学习如何最好地将自己定位为候选人或新的R代码!
(想练习仅将数据导入R吗?请尝试本教程, 以在R中导入数据。)
内容
- 获取数据:Web爬取和解析
- 探索页面结构
- 解析JSON对象
- 将数据合并到数据表中
- 清理数据
- 探索数据:第一步
- H1B签证数据的后续步骤
介绍
上周, srcmini的博客”数据可以帮助你的H-1B签证申请”向你展示了多年来对H-1B数据的分析结果。现在, 该动手动手, 自己分析数据, 看看还能找到什么! Ted Kwartler将通过一系列R教程指导你完成此过程。
我在一个德克萨斯州的律师事务所里有一个朋友, 该律师事务所提供H1B签证。 H1B是美利坚合众国的非移民签证, 允许美国雇主临时雇用外国工人从事特殊职业。显然, 被接受非常困难, 因为与成千上万的申请人相比, 签证供应有限。
尽管这是轶事, 但我还是决定自己探索数据, 以期帮助合格的候选人知道美国是一个受欢迎的地方!
获取数据:Web爬取和解析
我的srcmini同事向我指出了这个网站, 它是一个简单的网站, 包含2012年至2016年的H1B数据。该网站声称将2M H1B应用程序组织到一个表中。
我决定以编程方式收集此数据(阅读:网络抓取), 因为我一生都不会复制/粘贴!
如你所见, 下图显示了网站的一部分, 显示了波士顿的H1B数据:
本教程将使用的库包括用于解析JSON对象的jsonlite, “收获” HTML的rvest, pbapply(个人喜好, 因为它在基本应用函数中添加了进度条)和data.table, 这可以改善R在大型数据帧上的性能。
library(jsonlite)
library(rvest)
library(pbapply)
library(data.table)
探索页面结构
浏览站点时, 你将意识到搜索表单会建议预填充选项。例如, 在”城市”字段中键入” B”将显示以下建议的模式。
下图显示了当我键入” B”时的预填充选项:
这意味着你可以使用预填充作为查询站点的有效方法。
使用Chrome, 你可以重新加载, 然后右键单击以”检查”页面, 然后在开发人员面板中导航至”网络”, 最后在页面上键入” B”以加载模式。
浏览网络面板链接, 你将发现一个PHP查询, 返回类似城市的JSON对象。
目标是首先收集所有建议的城市, 然后使用该列表从H1B数据中刮取大量页面。
浏览前一个URL时, 你会注意到它以字母结尾。因此, 你可以将paste0()与URL基础, http://h1bdata.info/cities.php?term =和字母一起使用。每个字母的值都将循环使用。字母对象是从” a”到” z”的内置R向量。 json.cities对象是URL的向量, 从a到z, 其中包含所有预填充建议作为JSON。
json.cities<-paste0('http://h1bdata.info/cities.php?term=', letters)
解析JSON对象
json.cities对象是R必须读取的26个链接的向量。使用lapply()或pblapply()以及fromJSON, R将解析每个JSON对象以创建all.cities。你将结果嵌套在unlist中, 因此输出是一个简单的字符串向量。使用此代码, 你可以将所有预填充城市组织到一个向量中, 以用于构建包含数据的实际网页。
all.cities<-unlist(pblapply(json.cities, fromJSON))
为了减少单个页面的加载时间, 你可以决定将两个参数(城市和年份)传递到每个网页查询中。例如, 2012年的波士顿H1B数据, 然后是2013年的波士顿, 依此类推。
创建因子组合时要使用的一个很棒的功能是expand.grid()。
在下面的代码中, 你看到传递了城市信息, all.cities, 然后使用seq()从2012年传递到2016年。该函数创建了5000多个城市年份组合。 expand.grid()以编程方式创建Boston 2012, Boston 2013, Boston 2014等, 因为每个城市和每年代表唯一的因子组合。
city.year<-expand.grid(city=all.cities, yr=seq(2012, 2016))
有些城市(例如洛杉矶)是两个必须为URL编码的单词。 url_encode()函数将” Los Angeles”更改为Los%20Angeles以验证地址。你传入整个向量, 并且url_encode()将按行工作:
city.year$city<-urltools::url_encode(as.character(city.year$city))
最后, 再次使用paste0()函数将基本URL连接到city.year中的city和state组合。在此处查看示例链接。
all.urls<-paste0('http://h1bdata.info/index.php?em=&job=&city=', city.year[, 1], '&year=', city.year[, 2])
从页面中提取信息
完成上述步骤后, 你可以创建一个名为main的自定义函数, 以从每个页面收集数据。
这是使用rvest提供的功能的简单工作流程。
首先, URL被接受并且read_html()解析页面内容。接下来, 从所有其他HTML信息中选择页面的单个html_table。 main函数将x对象转换为data.table, 以便可以将其有效地存储在内存中。
最后, 在关闭main之前, 你可以添加Sys.sleep, 这样就不会被视为DDOS攻击。
main<-function(url.x){
x<-read_html(url.x)
x<-html_table(x)
x<-data.table(x[[1]])
return(x)
Sys.sleep(5)
}
我们去获取数据!
我喜欢使用进度条pblapply(), 因此可以跟踪抓取进度。
你只需在pblapply()函数中传递all.urls和main函数。 R立刻开始加载页面, 收集表并将data.table保存在该页面的内存中。每个URL依次收集并保存在内存中。
all.h1b<-pblapply(all.urls, main)
将数据合并到数据表中
!
那花了几个小时!
此时, all.h1b是数据表的列表, 每页一个。要将列表统一为一个数据表, 可以使用rbindlist。这类似于do.call(rbind, all.h1b), 但速度更快。
all.h1b<-rbindlist(all.h1b)
最后保存数据, 这样你就不必再次执行此操作。幸运的是, 我在这里保存了一份副本。
write.csv(all.h1b, 'h1b_data.csv', row.names=F)
清理数据
即使你抓取了数据, 也需要一些其他步骤才能将其转换为可管理的格式。
你使用lubridate帮助组织日期。你还可以使用stringr, 它为字符串操作提供包装器。
library(lubridate)
library(stringr)
尽管这是个人喜好, 但我喜欢使用scipen = 999。它不是强制性的, 但它摆脱了科学的记号。
options(scipen=999)
事实证明, 网络抓取记录捕获了2M H1B记录中的1.8M。我认为180万就足够了。因此, 让我们使用fread()加载数据:此函数类似于read.csv, 但是它是更有效的”快速友好的文件整理器”。
h1b.data<-fread('h1b_data.csv')
抓取的数据列名称为大写且包含空格。
因此, 按名称引用它们很麻烦, 因此你要做的第一件事就是重命名它们。
重命名列名称需要在赋值运算符(<-)的两侧都有函数。在左侧使用colnames()并传入数据框。在运算符的右侧, 你可以传入字符串向量。
在此示例中, 你首先使用原始名称, 并使用tolower()将其变为小写。在第二行中, 应用全局替换函数gsub()。
当gsub()识别出一个模式(在这种情况下为空格)时, 它将用第二个参数下划线替换所有实例。最后, 你需要告诉gsub()在代表现在小写的数据帧列的名称(h1b.data)中执行替换。
colnames(h1b.data)<-tolower(names(h1b.data))
colnames(h1b.data)<-gsub(' ', '_', names(h1b.data))
探索数据时使用的第一个函数之一是tail()函数。此函数将返回底部的行。在这里, tail()将返回最后8行。
这有助于你快速查看数据形状和向量的外观。
tail(h1b.data, 8)
接下来, 我总是检查向量的类。使用网络抓取的数据, 数值或因子可以成为文本。
你会看到现在更正类可以避免以后感到沮丧!
使用apply()函数, 可以传递h1b.data, 然后传递2和函数类。由于你选择了2, R将检查每个列的类并将其返回到控制台。你可以将apply()与1结合使用, 以逐行应用函数, 但在这种情况下没有帮助。
apply(h1b.data, 2, class)
哦!
所有列均为”字符”, 必须更正。
我将向你展示如何更改日期列之一, 然后将其他列留给你。使用tail(), 你可以检查错误分类日期的最后6行。
要更正日期, 需要将/斜杠更改为-。再次使用gsub()搜索/, 然后将其替换为-。
tail(h1b.data$submit_date)
h1b.data$submit_date<-gsub('/', '-', h1b.data$submit_date)
在破折号到位的情况下, 你应用mdy()代表”月, 日, 年”。这是因为日期是按该顺序声明的。如果顺序不同, 你将相应地重新排列mdy字母。
为了确保正确更改了列, 请重新检查尾部并检查类。 tail()应打印与” 2016-03-11 UTC”相似的日期, 并且向量类应为” POSIXct”而不是” character”。
h1b.data$submit_date<-mdy(h1b.data$submit_date)
tail(h1b.data$submit_date)
class(h1b.data$submit_date)
对于这种类型的分析, 最好只将日期中的月份和年份提取到新列中。在下面的代码中, 你看到声明了两个新列$ submit_month和$ submit_yr。
在lubridate中, month()函数可以应用于整个列, 以从日期中提取月份值。 year()函数类似地接受date列以创建h1b.data $ submit_yr。使用head()函数时, 现在应该看到已经创建了两个新列。
h1b.data$submit_month<-month(h1b.data$submit_date, label=T)
h1b.data$submit_yr<-year(h1b.data$submit_date)
head(h1b.data)
接下来, 让我们检查$ base_salary列。它在千位中有一个逗号, R认为它是一个字符, 因此必须进行更改。 gsub()再次设法删除逗号并将其替换为空字符。然后将as.numeric()应用于h1b.data $ base_salary, 以将值正式更改为数字。
你可以在第三行中使用head()检查新矢量的一部分。
h1b.data$base_salary<-gsub(', ', '', h1b.data$base_salary)
h1b.data$base_salary<-as.numeric(h1b.data$base_salary)
head(h1b.data$base_salary)
切割此数据的另一种方法是按状态。当你检查h1b.data $ location列时, 你会看到城市和州由逗号分隔。下面的代码使用str_split_fixed()分隔第一个逗号上的位置信息。只需传入列, 分隔字符和要返回的列数即可。结果状态对象是大矩阵, 具有与h1b.data相同的行数和2列。
state<-str_split_fixed(h1b.data$location, ', ', 2)
代码列的后两行将单独的状态向量$ city和$ state绑定到h1b.data。矢量不是完美的, 因为拼写可能会有所不同, 例如” Winston Salem”和” Winston-Salem”。总体而言, 此方法对于简单的EDA来说已经足够好, 但请记住, 在其他分析中可能需要一些术语汇总。
h1b.data$city<-state[, 1]
h1b.data$state<-state[, 2]
探索数据:第一步
如果你正在申请H1B签证, 你想知道哪些州最能增加我被录取的机会。 table()函数用于对分类变量进行计数, 可轻松应用于h1b.data $ state。在第二行中, 你可以创建一个小的数据框来捕获状态名称和已记录的H1B数据。
state.tally<-table(h1b.data$state)
state.tally<-data.frame(state=names(state.tally), h1b=as.vector(state.tally))
使用state.tally和barplot()将按状态创建H1B值的基本条形图。第二个参数name.arg声明条形标签, 而las = 3告诉R垂直放置标签。你可以从逗号拆分中看到一些草率的位置, 但要点很明确……H1B申请人可能在CA, NJ, NY或TX。
barplot(state.tally$h1b, names.arg = names(table(h1b.data$state)), las=3)
你会看到2012年至2016年的州H1B签证总数。
接下来, 让我们尝试了解H1B签证与外界事实之间的关系。为简单起见, R有一个内置的数据集, 称为state.x77。这是一个包含50行的矩阵, 每个州1行, 并且提供了诸如1977年美国人口普查中的人口和预期寿命之类的事实。
提示:在你自己的分析中使用更新的数据源。
目前, 使用state.x77是一个很好的例子。使用head()检查此内置数据集。
head(state.x77)
让我们将这些信息与state.tally数据合并到更大的数据框中, 以了解关系。为此, 创建一个state.data数据框, 其中包含状态缩写, state.abb和旧的1977年人口普查数据。然后调用并传递state.tally和state.data的合并。
你可以显式声明state列作为要加入的向量。通过索引第15到20行来检查第三行中数据帧的一部分。
state.data<-data.frame(state=state.abb, state.x77)
state.data<-merge(state.tally, state.data, by='state')
state.data[15:20, ]
EDA的基本功能是:将打印两个变量之间的相关性。
请记住, 相关性的范围是-1到1:0, 这意味着变量没有相关性, 并且可能不相关。接近1的数字表示值与R编程和收入一样正相关(希望如此)。负数表示关系朝相反的方向移动, 例如R编程和社交生活!
此代码将cor应用于1977年的州人口和当前的H1B记录。请记住, 该代码旨在说明尽管存在时间上的不匹配, 但如何执行分析。你可以更改$ Population以引用数据框中的另一个向量。
cor(state.data$Population, state.data$h1b)
研究变量关系的另一种方法是使用散点图, 尤其是散点图矩阵。使用pairs()快速绘制散点图矩阵。下面的代码使用公式定义关系。每列都单独声明, 中间有一个加号。 data参数接受数据框, 并且main仅声明图标题。
pairs(~ h1b + Population + Income, data = state.data, main='h1b relationships')
你会看到此散点图矩阵将H1B计数可视化为状态”人口和收入”。
你可以观察人口与h1b之间的关系。这在直觉上是有道理的, 因为人口更多的州将有更多的工作机会需要H1B签证。
要从矩阵”放大”为单个图, 只需调用plot()并传入两个变量即可。
plot(state.data$Income, state.data$h1b, main = 'Income to H1B')
H1B签证数据的后续步骤
你才刚刚开始我们的H1B签证探索!这个数据集非常丰富, 在下一篇文章中, 你将探索薪水数据, 摆脱异常值, 并制作更具吸引力的ggplot2视觉效果。
你还将通过逐步探索H1B地位以及顶级雇主来建立这些EDA概念。我已经向你展示的很酷的视觉效果之一叫做箱形图, 它使用以下代码显示了按H1B签证状态划分的工资分配:
ggplot(h1b.data) +
geom_boxplot(aes(factor(case_status), base_salary, fill=as.factor(case_status))) +
ylim(0, 100000) +
theme_gdocs() +
scale_fill_gdocs() +
theme(axis.text.x=element_blank())
请继续关注”使用R探索H-1B数据”教程系列的另一部分!同时, 请考虑查看我们的R数据框架教程, R课程中的导入数据或R with dplyr课程中的R数据操作。
评论前必须登录!
注册