如何计算区县质心的地理距离:R 语言和 Stata 的对比使用

最近有小伙伴问到了关于如何计算一些县到另外一些县质心最小距离的问题。今天我们一起来看下如何在 R 语言以及 Stata 中进行这样的计算。

首先我们使用 Stata 生成一些样本:

use county_db.dta, clear
keep if inlist(省, "江苏省", "安徽省", "浙江省", "山东省")
sample 10
keep 县 县代码
ren 县 试点县
export excel using 政策试点县.xlsx, replace first(variables)

use county_db.dta, clear
keep if inlist(省, "江苏省", "安徽省", "浙江省", "山东省")
keep 县 县代码
ren 县 样本县
export excel using 样本县.xlsx, replace first(variables)

我们的目标就是计算所有的样本县与政策试点县的最小距离。

使用 R 语言的 sf 包

R 语言中通常使用 sf 包进行矢量数据的地理计算。

首先加载相关 R 包,读取数据:

library(tidyverse)
library(sf)
read_sf("2020行政区划/县.shp") %>%
select(-contains("类型")) %>%
mutate(县代码 = as.numeric(县代码)) -> county

readxl::read_xlsx("政策试点县.xlsx") -> cty1
cty1

readxl::read_xlsx("样本县.xlsx") -> cty2
cty2

由于 cty1 和 cty2 中都没有地理信息,所以需要和 county 匹配下:

cty1 %>%
left_join(county, by = join_by("县代码")) %>%
st_sf() %>%
st_centroid() -> cty1

cty2 %>%
left_join(county, by = join_by("县代码")) %>%
st_sf() %>%
st_centroid() -> cty2

st_centroid() 函数的作用是计算每个区县的质心。

然后就可以使用 st_distance() 计算距离矩阵了:

cty2 %>%
st_distance(cty1) -> distmat

dim(distmat)

由于计算得到的距离矩阵中并没有县的信息,所以我们还得准备一个样本县的信息:

cty2 %>%
st_drop_geometry() %>%
select(样本县, 县代码) -> ctydf2

然后就可以求距离矩阵每一行的最小值,再和上面的样本县合并了:

apply(distmat, 1, min) %>%
as_tibble() %>%
bind_cols(ctydf2, .) %>%
mutate(value = value / 1000) %>%
rename(dist_km = value) -> resdf

resdf

resdf %>%
haven::write_dta("计算结果.dta")

这里计算的就是大圆距离。sf 包的计算规则是,如果计算的对象是 WGS84 坐标系,返回的是大圆距离,如果不是,返回的就是欧几里得距离。

使用 Stata 的 geodist 命令

Stata 的 geodist 命令计算的也是大圆距离。

首先读取两个数据:

import excel using "政策试点县.xlsx", clear first
save cty1, replace

import excel using "样本县.xlsx", clear first
foreach i of varlist _all {
cap ren `i' `i'2
}
save cty2, replace

注意,由于这两个数据的变量名有重复,所以为了下面的 corss 操作,我把 cty2 数据的变量名都加了个 2。

Stata 中可以使用 shp2dta 命令把 shp 格式的矢量数据转换成 dta 数据,使用 gencentroids(centroids) 选项可以计算出每个区域的质心。

*- 计算区县质心
shp2dta using 2020行政区划/县.shp, database(county_db) coordinates(county_coord) genid(ID) replace gencentroids(centroids)

然后得到的 county_db.dta 数据里面就有质心坐标 x_centroids(经度)和 y_centroids(纬度)了。

下面我们把 cty1 和 cty2 cross 起来,并且匹配上每个县的质心:

*- 生成交叉数据
use cty2, clear
cross using cty1
merge m:1 县代码 using county_db
drop if _m == 2
drop _m

ren x_centroids lon1
ren y_centroids lat1
drop ID 省 - 县
drop 县代码
ren 县代码2 县代码
merge m:1 县代码 using county_db
drop if _m == 2
drop _m

ren x_centroids lon2
ren y_centroids lat2
drop ID 省 - 县

然后就可以使用 geodist 命令计算距离了:

geodist lat1 lon1 lat2 lon2, gen(dist_km2)
collapse (min) dist_km, by(县代码 样本县2)
ren 样本县2 样本县

*> +-------------------------------+
*> | 样本县 县代码 dist_km2 |
*> |-------------------------------|
*> 1. | 玄武区 320102 15.176004 |
*> 2. | 秦淮区 320104 14.268666 |
*> 3. | 建邺区 320105 8.1611449 |
*> 4. | 鼓楼区 320106 17.070362 |
*> 5. | 浦口区 320111 17.780959 |
*> |-------------------------------|
*> 6. | 栖霞区 320113 0 |
*> 7. | 雨花台区 320114 0 |
*> 8. | 江宁区 320115 15.889987 |
*> 9. | 六合区 320116 28.290099 |
*> 10. | 溧水区 320117 47.285335 |
*> +-------------------------------+

R 和 Stata 的对比使用

也可以把 R 语言的结果也拷贝过来对比下:

merge 1:1 样本县 县代码 using 计算结果
ren dist_km R语言计算结果
ren dist_km2 Stata计算结果
drop _m

list in 1/10
*> +-------------------------------------------+
*> | 样本县 县代码 Stata~果 R语言~果 |
*> |-------------------------------------------|
*> 1. | 三门县 331022 52.276384 52.446334 |
*> 2. | 上城区 330102 46.374544 46.382117 |
*> 3. | 上虞区 330604 31.852149 31.801806 |
*> 4. | 下城区 330103 44.496137 44.453995 |
*> 5. | 东台市 320981 97.922319 97.990662 |
*> |-------------------------------------------|
*> 6. | 东平县 370923 58.076212 58.102572 |
*> 7. | 东昌府区 371502 51.472837 51.525031 |
*> 8. | 东明县 371728 37.43178 37.403865 |
*> 9. | 东海县 320722 39.506999 39.382455 |
*> 10. | 东港区 371102 24.502146 24.521978 |
*> +-------------------------------------------+

结果差异很小~ 也可以画图对比下:

tw sc Stata计算结果 R语言计算结果 || lfit Stata计算结果 R语言计算结果, ///
xti("R 语言计算结果") yti("Stata 计算结果") ///
ti("如何计算区县质心的地理距离:R 语言和 Stata 的对比使用") ///
subti("数据处理&计算:微信公众号 RStata") ///
leg(off)
gr export pic1.png, width(4800) replace

点击这里跳转到 RStata 短书平台获取附件:如何计算区县质心的地理距离:R 语言和 Stata 的对比使用

评论