基于点距离反转数据框列表元素(仅用Base R实现)
问题需求
我有一个存储点数据的dataframe列表trace,数据如下:
# > trace # [[1]] # counter X Y L1 id ref # 1: 1 12.0165469 47.9982008 1 533 EBE 20 # 2: 2 12.0164185 47.9981120 1 533 EBE 20 # 3: 3 12.0163478 47.9980425 1 533 EBE 20 # 4: 4 12.0162577 47.9979528 1 533 EBE 20 # 5: 5 12.0161357 47.9978117 1 533 EBE 20 # 6: 6 12.0160016 47.9976360 1 533 EBE 20 # # [[2]] # counter X Y L1 id ref # 1: 7 11.8333238 48.1269572 2 4665 St 2081 # 2: 8 11.8330935 48.1267962 2 4665 St 2081 # # [[3]] # counter X Y L1 id ref # 1: 9 11.8338450 48.1271528 3 4666 St 2081 # 2: 10 11.8335776 48.1270632 3 4666 St 2081
我需要遍历这个列表,完成以下操作:
- 计算当前dataframe最后一个点与下一个dataframe第一个点的距离(记为
distance_1) - 计算当前dataframe最后一个点与下一个dataframe最后一个点的距离(记为
distance_2) - 如果
distance_1 > distance_2,则反转下一个dataframe的行顺序
我的伪代码如下:
compare<-function(data_frame,next_dataframe){ distance_1<-geosphere::distGeo(p1=data_frame[nrow(data_frame),],p2=next_dataframe[first(next_dataframe),]) distance_2<-geosphere::distGeo(p1=data_frame[nrow(data_frame),],p2=next_dataframe[nrow(next_dataframe),]) if (distance_1>distance_2) next_dataframe<-as.data.frame(next_dataframe, 2, rev) } lapply(x, function(x)compare(x,x+1))
期望结果如下(其中第3个dataframe的行被反转):
# > trace # [[1]] # counter X Y L1 id ref # 1: 1 12.0165469 47.9982008 1 533 EBE 20 # 2: 2 12.0164185 47.9981120 1 533 EBE 20 # 3: 3 12.0163478 47.9980425 1 533 EBE 20 # 4: 4 12.0162577 47.9979528 1 533 EBE 20 # 5: 5 12.0161357 47.9978117 1 533 EBE 20 # 6: 6 12.0160016 47.9976360 1 533 EBE 20 # # [[2]] # counter X Y L1 id ref # 1: 7 11.8333238 48.1269572 2 4665 St 2081 # 2: 8 11.8330935 48.1267962 2 4665 St 2081 # # [[3]] # counter X Y L1 id ref # 2: 10 11.8335776 48.1270632 3 4666 St 2081 # 1: 9 11.8338450 48.1271528 3 4666 St 2081
请问能否不使用外部包,仅用Base R实现该需求?列表的可复现代码如下:
trace<-list( structure( list( counter = 1:6, X = c( 12.0165469, 12.0164185, 12.0163478, 12.0162577, 12.0161357, 12.0160016 ), Y = c( 47.9982007999381, 47.9981119999382, 47.9980424999382, 47.9979527999382, 47.9978116999382, 47.9976359999383 ), L1 = c(1, 1, 1, 1, 1, 1), id = c(533, 533, 533, 533, 533, 533), ref = c("EBE 20", "EBE 20", "EBE 20", "EBE 20", "EBE 20", "EBE 20") ), row.names = c(NA,-6L), class = c("data.table", "data.frame") ), structure( list( counter = 7:8, X = c(11.8333238, 11.8330935), Y = c(48.1269571999072, 48.1267961999073), L1 = c(2, 2), id = c(4665, 4665), ref = c("St 2081", "St 2081") ), row.names = c(NA,-2L), class = c("data.table", "data.frame") ), structure( list( counter = 9:10, X = c(11.833845, 11.8335776), Y = c(48.1271527999072, 48.1270631999072), L1 = c(3, 3), id = c(4666, 4666), ref = c("St 2081", "St 2081") ), row.names = c(NA,-2L), class = c("data.table", "data.frame") ) )
Base R实现方案
完全可以用Base R实现,核心是先实现球面距离计算逻辑替代外部包函数,再遍历列表处理每个后续dataframe:
1. 实现Base R版球面距离计算函数
用Haversine公式实现WGS84坐标系下的大圆距离计算(和geosphere::distGeo逻辑一致):
# 计算两点间的球面距离(单位:米) dist_sphere <- function(p1, p2) { # 提取经纬度(X为经度,Y为纬度) lon1 <- p1[["X"]] lat1 <- p1[["Y"]] lon2 <- p2[["X"]] lat2 <- p2[["Y"]] # 转为弧度 lon1 <- lon1 * pi / 180 lat1 <- lat1 * pi / 180 lon2 <- lon2 * pi / 180 lat2 <- lat2 * pi / 180 # Haversine公式计算大圆距离 dlon <- lon2 - lon1 dlat <- lat2 - lat1 a <- sin(dlat/2)^2 + cos(lat1) * cos(lat2) * sin(dlon/2)^2 c <- 2 * atan2(sqrt(a), sqrt(1-a)) r <- 6378137 # WGS84地球半径(米) return(r * c) }
2. 遍历列表处理dataframe
用for循环遍历列表(因为需要修改后续元素,lapply不适合这种有状态的迭代):
# 遍历列表,处理每个后续dataframe for (i in seq_along(trace)[-length(trace)]) { current_df <- trace[[i]] next_df <- trace[[i+1]] # 获取需要计算距离的点 last_current <- current_df[nrow(current_df), c("X", "Y")] first_next <- next_df[1, c("X", "Y")] last_next <- next_df[nrow(next_df), c("X", "Y")] # 计算两个距离 distance_1 <- dist_sphere(last_current, first_next) distance_2 <- dist_sphere(last_current, last_next) # 判断是否反转下一个dataframe的行顺序 if (distance_1 > distance_2) { trace[[i+1]] <- next_df[nrow(next_df):1, ] } }
3. 验证结果
运行代码后查看trace[[3]],可以看到行已按需求反转:
trace[[3]] # counter X Y L1 id ref # 2: 10 11.83358 48.12706 3 4666 St 2081 # 1: 9 11.83385 48.12715 3 4666 St 2081
内容的提问来源于stack exchange,提问作者Andreas
相关产品推荐
相关产品推荐

