CRAN上的包都是干什么的?

在之前的推文 R 和 RStudio 的安装 的结尾,我写了段爬取 CRAN 上的所有 R 包的名称、发布日期和标题的代码,但是我只使用了前两个变量,进行绘图,没有提标题的事情,那么标题可以用来干什么呢?标题当然是描述该包的主要功能了,通过简单的词频统计,我们就能绘制一幅词云图观察 CRAN 上的 R 包的关键词是哪些了,首先还是爬取清华镜像源的那个表格:

可视化 Expatistan 网站上的各国生活成本指数数据

本周的小项目作业是“爬取 Expatistan 网站上的各国生活成本数据并绘制一幅世界地图进行展示”。

  1. 数据源:Expatistan

  2. 世界地图的底图数据:tmap
    包内有一个 World 数据,调用方法:

R
1
data("World", package = "tmap")
  1. 爬取数据的 R 包,可以用
    rvest

Tips: 可能需要到的函数:read_html,html_nodes,html_table;

  1. 绘制地图的 R 包,ggplot + sf (本周有教程),用 tmap 也行。

  2. 拓展作业:可以再绘制一些其他的图来展示各国生活成本的排名。


参考结果

爬取数据

这种表格数据用 rvest 包爬取非常容易:

R
1
2
3
4
5
6
7
library(tidyverse)
library(hrbrthemes)
library(rvest)
# 把网址保存成一个名为 url 的变量:
url <- "https://www.expatistan.com/cost-of-living/country/ranking"
# 使用 read_html() 函数读取解析网页文件,保存为名为 html 的变量:
html <- read_html(url)

解析得到的 html 是个 xml_document,这是一种结构性的数据,我们可以使用
html_nodes() 函数从中找寻某个节点,通常找寻的办法有两个:CSS 和
XPath,都可以用,首先我们用 xpath:

R
1
2
3
4
5
html %>% 
html_nodes(xpath = '//*[@id="content"]/div/div[1]/div[1]/table')

## {xml_nodeset (1)}
## [1] <table class="country-ranking centered">\n<thead><tr>\n<th>Ranking</th>\n ...

table 标签对应的就是我们想要爬取数据的这个表格。那么这个 xpath
从哪来的呢?

或者我们可以用 CSS 选择:

R
1
2
3
4
5
html %>% 
html_nodes(css = '#content > div > div.block.first.comparison > div.prices > table')

## {xml_nodeset (1)}
## [1] <table class="country-ranking centered">\n<thead><tr>\n<th>Ranking</th>\n ...

CSS 选择器是这么来的

两种方式的效果是一样的,至于选择哪种就看你的偏好了。

得到了 table 所在的节点之后呢,我们可以使用 html_table()
函数解析表格,解析之后再转化为 tibble 数据框并赋值给 df 变量:

R
1
2
3
4
5
html %>% 
html_nodes(xpath = '//*[@id="content"]/div/div[1]/div[1]/table') %>%
html_table() %>%
.[[1]] %>%
as_tibble() -> df

完整的表格是这样的:

R
1
2
df %>% 
DT::datatable()

我们再把这个数据整理一下,例如 Ranking 变量可以转换成数值型变量,
Price Index * 的名字也改改:

R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
library(stringr)
df <- df %>%
`colnames<-`(c("ranking", "country", "price_index")) %>%
mutate(ranking = str_remove_all(ranking, "[st nd rd th]")) %>%
type_convert()

df

## # A tibble: 95 x 3
## ranking country price_index
## <dbl> <chr> <int>
## 1 1 Cayman Islands 279
## 2 2 Hong Kong 230
## 3 3 Switzerland 226
## 4 4 Iceland 223
## 5 5 Bahamas 213
## 6 6 Norway 208
## 7 7 Singapore 197
## 8 8 Ireland 196
## 9 9 Denmark 189
## 10 10 Qatar 189
## # … with 85 more rows

先画个简单的柱状图吧!

R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 关于字体和主题的设置,请参考:https://czxa.top/tf/get-started-with-r-and-rstudio.html
enfont = "CascadiaCode-Regular"
library(forcats)
df %>%
slice(1:10) %>%
mutate(
country = fct_reorder(country, price_index)
) %>%
ggplot() +
geom_col(aes(x = country,
y = price_index,
fill = country)) +
awtools::a_dark_theme(enfont) +
theme(legend.position = "none") +
scale_fill_brewer(palette = "Paired") +
coord_flip() +
labs(y = "Price Index",
x = "",
title = "Cost of Living Ranking: Top 10",
subtitle = "Czech Republic = 100",
caption = "Data Source: Expatistan\nhttps://www.expatistan.com/cost-of-living/country/ranking") +
theme(plot.margin = grid::unit(c(1, 0.5, 0.5, 0.2), "cm"))

这是个世界地图的数据,最好的可视化当然是画世界地图了!

我们使用 ggplot2 + sf 绘制世界地图,底图使用 tmap 包中的 World,安装
tmap 包出错的小伙伴,可以从 TidyFriday 的 知识星球下载 “World.rds”
数据:

R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
library(ggplot2)
library(sf)
data("World", package = "tmap")
wdf <- World %>%
mutate(name = as.character(name)) %>%
left_join(df, by = c("name" = "country")) %>%
rename(`Price Index` = `price_index`)
ggplot(wdf) +
geom_sf(aes(geometry = geometry,
fill = `Price Index`),
color = "white", size = 0.05) +
theme_modern_rc(base_family = enfont,
plot_title_family = enfont,
subtitle_family = enfont,
caption_family = enfont) +
scale_fill_viridis_c() +
theme(plot.margin = grid::unit(c(1, 0.2, 0.3, 0.2), "cm")) +
labs(y = "",
x = "",
title = "Cost of Living Ranking by Country",
subtitle = "Czech Republic = 100",
caption = "Data Source: Expatistan\nhttps://www.expatistan.com/cost-of-living/country/ranking")

离散变量

价格指数是个连续变量,但是我们可以把它切割成分组的离散变量:

R
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
# 使用分位数进行切割,例如我们想分成 6 组
nclass = 6

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

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

## [1] "64 – 77" "77 – 89"
## [3] "89 – 104.5" "104.5 – 129.333333333333"
## [5] "129.333333333333 – 166" "166 – 226"

wdf <-
wdf %>%
mutate(
`Price Index` = cut(`Price Index`,
breaks = quantiles,
labels = labels,
include.lowest = TRUE)
)

unique(wdf$`Price Index`)

## [1] <NA> 77 – 89 129.333333333333 – 166
## [4] 64 – 77 166 – 226 89 – 104.5
## [7] 104.5 – 129.333333333333
## 6 Levels: 64 – 77 77 – 89 89 – 104.5 ... 166 – 226

绘制地图:

R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ggplot(wdf) + 
geom_sf(aes(geometry = geometry,
fill = `Price Index`),
color = "white", size = 0.05) +
theme_modern_rc(base_family = enfont,
plot_title_family = enfont,
subtitle_family = enfont,
caption_family = enfont) +
scale_fill_manual(values = awtools::a_palette) +
theme(plot.margin = grid::unit(c(1, 0.2, 0.3, 0.2), "cm")) +
labs(y = "",
x = "",
title = "Cost of Living Ranking by Country",
subtitle = "Czech Republic = 100",
caption = "Data Source: Expatistan\nhttps://www.expatistan.com/cost-of-living/country/ranking")

使用 tmap 包绘制地图

R
1
2
3
4
5
6
7
wdf2 <- World %>% 
mutate(name = as.character(name)) %>%
left_join(df, by = c("name" = "country")) %>%
rename(`Price Index` = `price_index`)
tmap::tmap_style("classic")
tmap::tm_shape(wdf2) +
tmap::tm_polygons("Price Index")

使用 highcharter 包绘制世界地图

似乎由于国家和地区的名字差异的问题,合并的有些问题,尽管我使用了
fuzzyjoin 包进行模糊连接:

R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
library(highcharter)
world <- download_map_data("custom/world-robinson-highres")
worlddf <- get_data_from_map(world)
worlddf <- worlddf %>%
fuzzyjoin::stringdist_left_join(df, by = c("name" = "country")) %>%
select(code = `hc-a2`, price_index)

hcmap("custom/world-robinson-highres",
data = worlddf, value = "price_index",
joinBy = c("hc-a2", "code"),
name = "Price Index",
dataLabels = list(
enabled = T,
format = '{point.name}'
),
borderColor = "#FAFAFA",
borderWidth = 0.1,
tooltip = list(
valueDecimal = 2
)) %>%
hc_title(text = "Cost of Living Ranking by Country") %>%
hc_subtitle(text = 'Data Source: <a href="https://www.expatistan.com/cost-of-living/country/ranking">Expatistan</a>', useHTML = TRUE) %>%
hc_add_theme(hc_theme_chalk())

我再试试用 Stata 完成这个图表的绘制。。。

爬取城市生活成本指数的排名

这个表在这里:https://www.expatistan.com/cost-of-living/index

爬取方法类似:

R
1
2
3
4
5
6
7
8
"https://www.expatistan.com/cost-of-living/index" %>% 
read_html() %>%
html_nodes(xpath = '//*[@id="ranking"]/div[1]/table') %>%
html_table() %>%
.[[1]] %>%
`colnames<-`(c("Ranking", "City", "Price Index")) %>%
as_tibble() %>%
DT::datatable()

编写 Shiny 文档

Shiny 文档和 R Markdown 文档不同的地方在于,它是实时运行的,我做了个 Shiny 文档:https://czxa.top/shiny/cost/ ,实时运行意味着每次你打开它的时候里面的代码就会自动运行一遍,所以这个文档上的表格和图表和原始网站上的始终是一致的。

Your browser is out-of-date!

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

×