名师讲堂|使用 R 语言提取上市公司所在位置及周边 8 个格点的夜间灯光亮度、判断是否加班及统计年加班天数

在论文「超时加班与劳动收入份额:基于卫星夜间灯光的经验证据」中,作者提出了这么一种判断企业是否加班的方法:

简单来说就是有三种标准:

  1. 时间维度标准:该公司处的夜间灯光亮度大于该年节假日夜间灯光亮度的中位数;
  2. 空间维度标准:该公司处的夜间灯光亮度大于周边8个网格的中位数;
  3. 时空双维度:同时满足上面的时间和空间标准的日子才被视为加班日。

今天的课程中我们将会讲解如何使用 R 语言提取上市公司所在位置及周边 8 个格点的夜间灯光亮度、判断是否加班及统计年加班天数。

在之前的课程中我们讲解过如何使用 R 语言爬取和处理 VNP46A2 的日度夜间灯光亮度数据:

名师讲堂|使用 R 语言下载和处理日度夜间灯光栅格数据:https://rstata.duanshu.com/#/brief/course/270178a4d6bb4ee2ba8b66ae71de3b8f

基于该课程讲解的方法处理得到的栅格数据可以从这里下载到:

2012~2024 年 VNP46A2 日度夜间灯光亮度栅格数据:https://rstata.duanshu.com/#/brief/course/ae5198d142b944108bcdecbd56e1fe36
注意:该夜间灯光亮度的单位是 0.1 nWatts/(cm^2 sr) 如果想转换成 nWatts/(cm^2 sr),需要把数值乘以 0.1 。

今天我们将会在该课程的基础上讲解。

除了日度夜间灯光栅格数据外,还会需要下面两个数据:

  1. 2000~2023 年上市公司注册地址与办公地址(含经纬度及其所处的省市区县):https://rstata.duanshu.com/#/brief/course/90fa75897e564a45beb32f7840be0787
  2. 2007~2024 年法定节假日及星期数据:https://rstata.duanshu.com/#/brief/course/63147c1f06924ddb89b152d1df84b78c

附件中我也给大家提供了示例数据:

  • 2024年日度夜间灯光数据(日度夜间灯光栅格数据)
  • 2024年法定节假日及星期数据.dta
  • 2023年上市公司办公地址经纬度.dta

由于截止讲义材料编写的时候还没有 2024 年的上市公司办公地址数据,所以我们只能假设 2023~2024 年上市公司没有搬家,也就是使用 23 年的数据作为 24 年的数据替代。

提取上市公司所在位置和周边 8 个相邻格点的夜光亮度

首先加载所需 R 包:

library(tidyverse)
library(terra)
library(sf)

索引所有的日度夜间灯光栅格数据:

fs::dir_ls("2024年日度夜间灯光数据", regexp = "tif$") -> fls

head(fls)

#> 2024年日度夜间灯光数据/2024-12-01.tif 2024年日度夜间灯光数据/2024-12-02.tif
#> 2024年日度夜间灯光数据/2024-12-03.tif 2024年日度夜间灯光数据/2024-12-04.tif
#> 2024年日度夜间灯光数据/2024-12-05.tif 2024年日度夜间灯光数据/2024-12-06.tif

不过 fs::dir_ls() 函数在 Windows 电脑上使用可能会遇到中文乱码问题,可以通过下面两种方法解决:

  1. 把文件夹的名字还有文件的名字都换成不带中文的;
  2. 改用 list.files();
paste0("2024年日度夜间灯光数据/", list.files("2024年日度夜间灯光数据")) -> fls
head(fls)

#> [1] "2024年日度夜间灯光数据/2024-12-01.tif"
#> [2] "2024年日度夜间灯光数据/2024-12-02.tif"
#> [3] "2024年日度夜间灯光数据/2024-12-03.tif"
#> [4] "2024年日度夜间灯光数据/2024-12-04.tif"
#> [5] "2024年日度夜间灯光数据/2024-12-05.tif"
#> [6] "2024年日度夜间灯光数据/2024-12-06.tif"

这样效果也是一样的,不过麻烦一些。

读取一个栅格数据:

rast(fls[length(fls)]) -> rst1
rst1

#> class : SpatRaster
#> dimensions : 14400, 16800, 1 (nrow, ncol, nlyr)
#> resolution : 0.004166667, 0.004166667 (x, y)
#> extent : 70, 140, 0, 60 (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84 (EPSG:4326)
#> source : 2024-12-31.tif
#> name : 2024-12-31
#> min value : 0
#> max value : 30000

绘图展示:

read_sf("九段线.geojson") -> jdx
plot(log(rst1))
plot(jdx, add = T)

读取上市公司办公地址经纬度数据:

haven::read_dta("2023年上市公司办公地址经纬度.dta") %>%
mutate(年份 = 2024) -> df

df

#> # A tibble: 5,354 × 5
#> 年份 股票代码 股票简称 办公地址_纬度 办公地址_经度
#> <dbl> <chr> <chr> <dbl> <dbl>
#> 1 2024 000001 平安银行 22.5 114.
#> 2 2024 000002 万科A 22.6 114.
#> 3 2024 000004 国华网安 22.6 114.
#> 4 2024 000006 深振业A 22.5 114.
#> 5 2024 000007 *ST 全新 22.6 114.
#> 6 2024 000008 神州高铁 39.9 116.
#> 7 2024 000009 中国宝安 22.6 114.
#> 8 2024 000010 美丽生态 22.6 114.
#> 9 2024 000011 深物业A 22.5 114.
#> 10 2024 000012 南玻A 22.5 114.
#> # ℹ 5,344 more rows

由于 terra 包里面并没有提供直接计算周边 8 个格点亮度中位数的方法,所以我们还是得手动编程解决。

一种办法是下面这样:

library(terra)

# 创建一个示例栅格数据
r <- rast(nrows=10, ncols=10, vals=1:100)

# 自定义函数:计算周围 8 个格点的中位数(排除中心点)
median_8_neighbors <- function(x) {
# 将中心点(第 5 个值)设为 NA
x[5] <- NA
# 计算剩余 8 个值的中位数
median(x, na.rm = TRUE)
}

# 定义 3x3 的窗口
w <- matrix(1, nrow=3, ncol=3)

# 使用 focal() 函数,应用自定义函数
r_median_8 <- focal(r, w=w, fun=median_8_neighbors)

# 查看结果
print(r_median_8)

#> class : SpatRaster
#> dimensions : 10, 10, 1 (nrow, ncol, nlyr)
#> resolution : 36, 18 (x, y)
#> extent : -180, 180, -90, 90 (xmin, xmax, ymin, ymax)
#> coord. ref. : lon/lat WGS 84 (CRS84) (OGC:CRS84)
#> source(s) : memory
#> name : lyr.1
#> min value : 11
#> max value : 90
# 获取某个特定点的值(例如第 5 行第 5 列的点)
point_value <- r_median_8[5, 5]
print(point_value)

#> lyr.1
#> 1 45

focal()函数是用来进行邻域计算的,这个方法看起来很方便,不过实际上我也这么做了,结果程序运行了半个月才运行完。

最初我以为 focal(r, w=3, median, na.rm)就可以了,后来发现这是计算是计算九个格点共同中位数的。

由于上述的方法极度低效,所以我们还是得更换方法。最后我发现还是使用矩阵进行提取最快。

这个图和分享的数据里面的不太一样是因为那个图绘制的时候给平安银行的经纬度设置成了早年的。

上图展示了「平安银行」的经纬度及周边的夜光栅格。假设我们的栅格数据就这么大,我们可以数一下,这个栅格的列数为:

  • ncol = 6;

如果从左到右对每个格子进行编号,那么可以数一数,平安银行所在格点的编号是:

  • 平安银行所在格点的编号是: 16;

那么它周边 8 个格点的编号分别为:

  • grid1: 16 - 6 - 1 = 9
  • grid2: 16 - 6 = 10
  • grid3: 16 - 6 + 1 = 11
  • grid4: 16 - 1 = 15
  • grid5: 16 = 16
  • grid6: 16 + 1 = 17
  • grid7: 16 + 6 - 1 = 21
  • grid8: 16 + 6 = 22
  • grid9: 16 + 6 + 1 = 23

因此我们只需要知道栅格数据的列数以及某个格点的索引就可以推出其周边 8 个格点的索引。

那么在整个栅格数据里面,平安银行所在格点为:

xy = cbind(lon = df$办公地址_经度[1], lat = df$办公地址_纬度[1])
terra::cellFromXY(rst1, xy) -> centerid

centerid

#> [1] 151059373
# 栅格数据转换成一维的矩阵
as.matrix(rst1) -> r_mat

dim(r_mat)

#> [1] 241920000 1
# 该处的值为
r_mat[centerid]

#> [1] 432

其周边 8 个格点的索引分别为:

# 栅格数据的列是
c <- ncol(rst1)

# 所以周边 8 个网格分别是
id1 = centerid - c - 1
id2 = centerid - c
id3 = centerid - c + 1
id4 = centerid - 1
id5 = centerid
id6 = centerid + 1
id7 = centerid + c - 1
id8 = centerid + c
id9 = centerid + c + 1

所以所有经纬度及周边网格的索引是:

df %>%
mutate(z = map2(办公地址_经度, 办公地址_纬度, function(x, y){
xy = cbind(lon = x, lat = y)
terra::cellFromXY(rst1, xy) -> centerid
id1 = centerid - c - 1
id2 = centerid - c
id3 = centerid - c + 1
id4 = centerid - 1
id5 = centerid
id6 = centerid + 1
id7 = centerid + c - 1
id8 = centerid + c
id9 = centerid + c + 1
return(c(id1, id2, id3, id4, id5, id6, id7, id8, id9))
})) -> df2

df2

#> # A tibble: 5,354 × 6
#> 年份 股票代码 股票简称 办公地址_纬度 办公地址_经度 z
#> <dbl> <chr> <chr> <dbl> <dbl> <list>
#> 1 2024 000001 平安银行 22.5 114. <dbl [9]>
#> 2 2024 000002 万科A 22.6 114. <dbl [9]>
#> 3 2024 000004 国华网安 22.6 114. <dbl [9]>
#> 4 2024 000006 深振业A 22.5 114. <dbl [9]>
#> 5 2024 000007 *ST 全新 22.6 114. <dbl [9]>
#> 6 2024 000008 神州高铁 39.9 116. <dbl [9]>
#> 7 2024 000009 中国宝安 22.6 114. <dbl [9]>
#> 8 2024 000010 美丽生态 22.6 114. <dbl [9]>
#> 9 2024 000011 深物业A 22.5 114. <dbl [9]>
#> 10 2024 000012 南玻A 22.5 114. <dbl [9]>
#> # ℹ 5,344 more rows

提取所有上市公司该日 9 个格点的夜光亮度:

df2 %>%
mutate(value = map(z, ~tibble(index = 1:9, nightlight = r_mat[unlist(.x)]))) %>%
select(-z) %>%
unnest(value)

#> # A tibble: 48,186 × 7
#> 年份 股票代码 股票简称 办公地址_纬度 办公地址_经度 index nightlight
#> <dbl> <chr> <chr> <dbl> <dbl> <int> <dbl>
#> 1 2024 000001 平安银行 22.5 114. 1 355
#> 2 2024 000001 平安银行 22.5 114. 2 409
#> 3 2024 000001 平安银行 22.5 114. 3 410
#> 4 2024 000001 平安银行 22.5 114. 4 431
#> 5 2024 000001 平安银行 22.5 114. 5 432
#> 6 2024 000001 平安银行 22.5 114. 6 493
#> 7 2024 000001 平安银行 22.5 114. 7 381
#> 8 2024 000001 平安银行 22.5 114. 8 381
#> 9 2024 000001 平安银行 22.5 114. 9 493
#> 10 2024 000002 万科A 22.6 114. 1 86
#> # ℹ 48,176 more rows

然后我们就可以循环所有的日子了,为了更高效,这里我用的是多线程循环:

dir.create("res")
library(parallel)
makeCluster(5) -> cl
clusterEvalQ(cl, ({
library(tidyverse)
library(terra)
})) -> tempres
clusterExport(cl, "df2")
parLapply(cl, fls, function(x){
if (!file.exists(paste0("res/", tools::file_path_sans_ext(basename(x)), ".rds"))) {
rast(x) -> rst
as.matrix(rst) -> r_mat
df2 %>%
filter(年份 == as.numeric(str_sub(basename(x), 1, 4))) %>%
mutate(value = map(z, ~tibble(index = 1:9, nightlight = r_mat[unlist(.x)]))) %>%
select(-z) %>%
unnest(value) %>%
mutate(date = ymd(tools::file_path_sans_ext(basename(x)))) %>%
write_rds(paste0("res/", tools::file_path_sans_ext(basename(x)), ".rds"))
}
}) -> tempres

合并提取结果:

fs::dir_ls("res") %>%
parLapply(cl, ., readr::read_rds) %>%
# lapply(readr::read_rds) %>%
bind_rows() -> df

# 日期提到前面
df %>%
select(日期 = date, everything()) -> df
df

#> # A tibble: 1,493,766 × 8
#> 日期 年份 股票代码 股票简称 办公地址_纬度 办公地址_经度 index
#> <date> <dbl> <chr> <chr> <dbl> <dbl> <int>
#> 1 2024-12-01 2024 000001 平安银行 22.5 114. 1
#> 2 2024-12-01 2024 000001 平安银行 22.5 114. 2
#> 3 2024-12-01 2024 000001 平安银行 22.5 114. 3
#> 4 2024-12-01 2024 000001 平安银行 22.5 114. 4
#> 5 2024-12-01 2024 000001 平安银行 22.5 114. 5
#> 6 2024-12-01 2024 000001 平安银行 22.5 114. 6
#> 7 2024-12-01 2024 000001 平安银行 22.5 114. 7
#> 8 2024-12-01 2024 000001 平安银行 22.5 114. 8
#> 9 2024-12-01 2024 000001 平安银行 22.5 114. 9
#> 10 2024-12-01 2024 000002 万科A 22.6 114. 1
#> # ℹ 1,493,756 more rows
#> # ℹ 1 more variable: nightlight <dbl>

计算中位数与比较是否加班

计算周边 8 个网格的中位数:

df %>%
filter(index != 5) %>%
group_by(日期, 年份, 股票代码, 股票简称) %>%
summarise(median8 = median(nightlight, na.rm = T)) %>%
ungroup() -> df2
df2

#> # A tibble: 165,974 × 5
#> 日期 年份 股票代码 股票简称 median8
#> <date> <dbl> <chr> <chr> <dbl>
#> 1 2024-12-01 2024 000001 平安银行 1320.
#> 2 2024-12-01 2024 000002 万科A 412
#> 3 2024-12-01 2024 000004 国华网安 478
#> 4 2024-12-01 2024 000006 深振业A 518.
#> 5 2024-12-01 2024 000007 *ST 全新 536.
#> 6 2024-12-01 2024 000008 神州高铁 502.
#> 7 2024-12-01 2024 000009 中国宝安 542.
#> 8 2024-12-01 2024 000010 美丽生态 678.
#> 9 2024-12-01 2024 000011 深物业A 1354.
#> 10 2024-12-01 2024 000012 南玻A 450
#> # ℹ 165,964 more rows

上市公司所在位置的:

df %>%
filter(index == 5) %>%
select(-index, -contains("度")) -> df1
df1

#> # A tibble: 165,974 × 5
#> 日期 年份 股票代码 股票简称 nightlight
#> <date> <dbl> <chr> <chr> <dbl>
#> 1 2024-12-01 2024 000001 平安银行 1407
#> 2 2024-12-01 2024 000002 万科A 413
#> 3 2024-12-01 2024 000004 国华网安 478
#> 4 2024-12-01 2024 000006 深振业A 633
#> 5 2024-12-01 2024 000007 *ST 全新 478
#> 6 2024-12-01 2024 000008 神州高铁 567
#> 7 2024-12-01 2024 000009 中国宝安 547
#> 8 2024-12-01 2024 000010 美丽生态 622
#> 9 2024-12-01 2024 000011 深物业A 1355
#> 10 2024-12-01 2024 000012 南玻A 602
#> # ℹ 165,964 more rows

读取节假日数据:

haven::read_dta("2024年法定节假日及星期数据.dta") %>%
mutate(is_legal_holidays = as.numeric(!is_work)) %>%
select(年份 = year, 日期 = date, 法定假日 = is_legal_holidays) -> df3

df3

#> # A tibble: 366 × 3
#> 年份 日期 法定假日
#> <dbl> <date> <dbl>
#> 1 2024 2024-01-01 1
#> 2 2024 2024-01-02 0
#> 3 2024 2024-01-03 0
#> 4 2024 2024-01-04 0
#> 5 2024 2024-01-05 0
#> 6 2024 2024-01-06 1
#> 7 2024 2024-01-07 1
#> 8 2024 2024-01-08 0
#> 9 2024 2024-01-09 0
#> 10 2024 2024-01-10 0
#> # ℹ 356 more rows

合并上面的三个数据:

df1 %>%
left_join(df2) %>%
left_join(df3) -> dfall

dfall

#> # A tibble: 165,974 × 7
#> 日期 年份 股票代码 股票简称 nightlight median8 法定假日
#> <date> <dbl> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 2024-12-01 2024 000001 平安银行 1407 1320. 1
#> 2 2024-12-01 2024 000002 万科A 413 412 1
#> 3 2024-12-01 2024 000004 国华网安 478 478 1
#> 4 2024-12-01 2024 000006 深振业A 633 518. 1
#> 5 2024-12-01 2024 000007 *ST 全新 478 536. 1
#> 6 2024-12-01 2024 000008 神州高铁 567 502. 1
#> 7 2024-12-01 2024 000009 中国宝安 547 542. 1
#> 8 2024-12-01 2024 000010 美丽生态 622 678. 1
#> 9 2024-12-01 2024 000011 深物业A 1355 1354. 1
#> 10 2024-12-01 2024 000012 南玻A 602 450 1
#> # ℹ 165,964 more rows

空间维度上的比较

dfall %>%
select(-contains("度")) %>%
mutate(是否加班_空间维度 = if_else(nightlight > median8, 1, 0)) -> dfall2

dfall2

#> # A tibble: 165,974 × 8
#> 日期 年份 股票代码 股票简称 nightlight median8 法定假日
#> <date> <dbl> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 2024-12-01 2024 000001 平安银行 1407 1320. 1
#> 2 2024-12-01 2024 000002 万科A 413 412 1
#> 3 2024-12-01 2024 000004 国华网安 478 478 1
#> 4 2024-12-01 2024 000006 深振业A 633 518. 1
#> 5 2024-12-01 2024 000007 *ST 全新 478 536. 1
#> 6 2024-12-01 2024 000008 神州高铁 567 502. 1
#> 7 2024-12-01 2024 000009 中国宝安 547 542. 1
#> 8 2024-12-01 2024 000010 美丽生态 622 678. 1
#> 9 2024-12-01 2024 000011 深物业A 1355 1354. 1
#> 10 2024-12-01 2024 000012 南玻A 602 450 1
#> # ℹ 165,964 more rows
#> # ℹ 1 more variable: 是否加班_空间维度 <dbl>

时间维度上的比较

# 计算年度法定节假日灯光亮度中位数
dfall2 %>%
filter(法定假日 == 1) %>%
group_by(年份, 股票代码, 股票简称) %>%
summarise(yearly_median = median(nightlight, na.rm = T)) %>%
ungroup() -> df4

dfall2 %>%
left_join(df4) %>%
mutate(是否加班_时间维度 = if_else(nightlight > yearly_median, 1, 0)) -> dfall3

dfall3

#> # A tibble: 165,974 × 10
#> 日期 年份 股票代码 股票简称 nightlight median8 法定假日
#> <date> <dbl> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 2024-12-01 2024 000001 平安银行 1407 1320. 1
#> 2 2024-12-01 2024 000002 万科A 413 412 1
#> 3 2024-12-01 2024 000004 国华网安 478 478 1
#> 4 2024-12-01 2024 000006 深振业A 633 518. 1
#> 5 2024-12-01 2024 000007 *ST 全新 478 536. 1
#> 6 2024-12-01 2024 000008 神州高铁 567 502. 1
#> 7 2024-12-01 2024 000009 中国宝安 547 542. 1
#> 8 2024-12-01 2024 000010 美丽生态 622 678. 1
#> 9 2024-12-01 2024 000011 深物业A 1355 1354. 1
#> 10 2024-12-01 2024 000012 南玻A 602 450 1
#> # ℹ 165,964 more rows
#> # ℹ 3 more variables: 是否加班_空间维度 <dbl>, yearly_median <dbl>,
#> # 是否加班_时间维度 <dbl>

同时满足两个维度

dfall3 %>%
rename(夜间灯光亮度 = nightlight, 周边8个网格夜光亮度中位数 = median8, 是否法定假日 = 法定假日, 法定节假日的年夜光亮度中位数 = yearly_median) %>%
mutate(同时满足时间和空间维度标准 = if_else(是否加班_时间维度 == 1 & 是否加班_空间维度 == 1, 1, 0)) -> dfall3

dfall3

#> # A tibble: 165,974 × 11
#> 日期 年份 股票代码 股票简称 夜间灯光亮度 周边8个网格夜光亮度中位数
#> <date> <dbl> <chr> <chr> <dbl> <dbl>
#> 1 2024-12-01 2024 000001 平安银行 1407 1320.
#> 2 2024-12-01 2024 000002 万科A 413 412
#> 3 2024-12-01 2024 000004 国华网安 478 478
#> 4 2024-12-01 2024 000006 深振业A 633 518.
#> 5 2024-12-01 2024 000007 *ST 全新 478 536.
#> 6 2024-12-01 2024 000008 神州高铁 567 502.
#> 7 2024-12-01 2024 000009 中国宝安 547 542.
#> 8 2024-12-01 2024 000010 美丽生态 622 678.
#> 9 2024-12-01 2024 000011 深物业A 1355 1354.
#> 10 2024-12-01 2024 000012 南玻A 602 450
#> # ℹ 165,964 more rows
#> # ℹ 5 more variables: 是否法定假日 <dbl>, 是否加班_空间维度 <dbl>,
#> # 法定节假日的年夜光亮度中位数 <dbl>, 是否加班_时间维度 <dbl>,
#> # 同时满足时间和空间维度标准 <dbl>

如果想统计加班天数,分年汇总即可:

dfall3 %>%
filter(同时满足时间和空间维度标准 == 1) %>%
count(股票代码, 股票简称, 年份) %>%
rename(年加班天数 = n) -> dfall4

如果想计算加班天数的比例,再计算一个各年的天数即可:

dfall3 %>%
count(股票代码, 股票简称, 年份) -> dayscount

dfall4 %>%
left_join(dayscount) %>%
mutate(ratio = 年加班天数 / n)

#> # A tibble: 5,272 × 6
#> 股票代码 股票简称 年份 年加班天数 n ratio
#> <chr> <chr> <dbl> <int> <int> <dbl>
#> 1 000001 平安银行 2024 8 31 0.258
#> 2 000004 国华网安 2024 9 31 0.290
#> 3 000006 深振业A 2024 7 31 0.226
#> 4 000007 *ST 全新 2024 4 31 0.129
#> 5 000008 神州高铁 2024 7 31 0.226
#> 6 000009 中国宝安 2024 8 31 0.258
#> 7 000010 美丽生态 2024 6 31 0.194
#> 8 000011 深物业A 2024 7 31 0.226
#> 9 000012 南玻A 2024 6 31 0.194
#> 10 000014 沙河股份 2024 10 31 0.323
#> # ℹ 5,262 more rows

这样我们就计算得到了这个指标~

最后再补充下上面图表的绘制代码:

library(tidyverse)
library(terra)
rast("2024年日度夜间灯光数据/2024-12-31.tif") -> rst

# 平安银行
library(sf)
tibble(name = "平安银行", lon = 114.0503, lat = 22.53655) %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326) -> dfsf

# 平安银行周围 3km
dfsf %>%
st_buffer(dist = units::set_units(1.2, "km")) -> dfbuffer

# 裁剪
rst %>%
terra::crop(terra::vect(dfbuffer)) -> smallrst

smallrst %>%
raster::raster() %>%
raster::rasterToPolygons() %>%
st_as_sf() -> rstpoly

# 平安银行周边的 9 个
rstpoly %>%
slice(9, 10, 11, 15, 16, 17, 21, 22, 23) %>%
mutate(id = paste0("grid", 1:9))-> rstpoly2

ggplot(rstpoly) +
geom_sf(aes(fill = `X2024.12.31`)) +
scico::scale_fill_scico(palette = "lajolla",
direction = 1) +
geom_sf(data = dfsf, shape = 17,
color = "red", size = 2) +
ggforce::geom_mark_circle(aes(x = 114.0503, y = 22.53655,
label = "平安银行"),
label.family = cnfont,
label.fontsize = 8,
label.fill = "#fff5e3",
label.colour = "#f39b7f",
con.size = 0.5,
con.colour = "#e64b35",
expand = unit(0, "mm"),
con.cap = unit(0, "mm")) +
geom_sf(data = st_union(rstpoly2),
fill = NA, color = "gray",
linewidth = 2) +
geom_sf_text(data = rstpoly2, aes(label = paste0(id, "\n", `X2024.12.31`)), family = cnfont) +
ggtext::geom_textbox(aes(x = 114.065, y = 22.54,
label = "周边夜光亮度中位数:<br>median(c(331, 331, 380, 342, <br>380, 342, 415, 415)) = 361"),
family = cnfont, size = 3,
vjust = 0, hjust = 0,
fill = "#fff5e3") +
ggtext::geom_textbox(aes(x = 114.065, y = 22.53,
label = "平安银行所在格点:416<br>按照空间维度标准,这天平安银行在<br>加班..."),
family = cnfont, size = 3,
hjust = 0, vjust = 1,
fill = "#fff5e3") +
ggthemes::theme_solarized_2(base_family = cnfont) +
theme(plot.background = element_rect(fill = "#fff5e3"),
plot.margin = grid::unit(rep(0.8, 4), "cm"),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
legend.position = "none") +
scale_x_continuous(limits = c(114.035, 114.075)) +
labs(title = "2024年12月31号这天平安银行及周边的夜间灯光亮度",
subtitle = "数据处理:微信公众号 RStata",
caption = "数据来源:VNP46A2 - VIIRS/NPP Gap-Filled Lunar BRDF\nAdjusted Nighttime Lights Daily L3 Global 500m Linear Lat Lon Grid\n<https://ladsweb.modaps.eosdis.nasa.gov/missions-and-measurements/products/VNP46A2/#overview>") -> p

ggsave("2024年12月31号这天平安银行及周边的夜间灯光亮度.png", device = png, width = 12, height = 7.2)

如何参加课程?

购买 RStata 名师讲堂会员即可参加该课程啦(之前的和未来的都可以参加)!

价格:2800/年 或者 4800/长期

购买会员可以从这里下单:https://rstata.duanshu.com/#/card/list/

名师讲堂会员权益:

  1. 参加每个月 3~4 次的名师讲堂课程;
  2. 参加平台上的其他 R 语言和 Stata 的课程;
  3. 以会员折扣价购买我们分享的数据资料(10 元/份);
  4. 课程内外的提问解答服务(课程外的尽量帮忙解决)。

* 如果发票可添加小编微信 r_stata (RStata 李老师)开具。如需数据资料,购买后可添加小编微信免费领取数据折扣卡。

更多关于 RStata 会员的更多信息可添加微信号 r_stata 咨询:

课程主页(点击文末的阅读原文即可跳转):

点击这里跳转到 RStata 短书平台获取附件:名师讲堂|使用 R 语言提取上市公司所在位置及周边 8 个格点的夜间灯光亮度、判断是否加班及统计年加班天数

评论