【问题标题】:How do I find the surface between lines based on intercept and slope which includes the origin?如何根据包括原点的截距和斜率找到线之间的表面?
【发布时间】:2022-11-07 22:18:59
【问题描述】:

我正在寻找一种方法来可视化多条直线之间的表面,这些直线通过它们的截距和斜率在数据框中定义。我正在寻找的表面是包含原点 (0, 0) 的表面。

线的数量可能会有所不同(即使在下面的简化示例中我只有 6 条),其中一些可能是多余的(即它们没有包围我正在寻找的表面,因为其他线更具约束力)。

让我们来看看这个简单的数据框:

df <- data.frame("Line" = c("A", "B", "C", "D", "E", "F"),
                 "Intercept" = c(4, 3, -2.5, -1.5, -5, -.5),
                 "Slope" = c(-1, 1, 2.4, -.6, -.8, .6))

ggplot2 绘制这些线:

ggplot(data = df) +
  geom_vline(xintercept = 0) +
  geom_hline(yintercept = 0) +
  geom_abline(mapping = aes(intercept = Intercept, slope = Slope),
              colour = "red") +
  coord_cartesian(xlim = c(-6, 6), ylim = c(-6, 6))

给我以下输出:

基本上我想找到包围原点 (0, 0) 的线之间的交点,忽略多余的线(在这种情况下,左下角,截距 = -5 和斜率 = -0.8)。然后将使用这 5 个交点来绘制凸包。

我的主要问题在于找到交点的约束线(下面的绿色点)以便能够找到蓝色表面。

问题:关于如何在 R 中处理这个问题的任何建议,理想情况下可以扩展到更大的数据帧(包括更多的约束和冗余行)?

附加问题:geom_abline() 没有团体美学类似于geom_line(),可用于识别线。有谁知道基于斜率和截距(或线的两个用户定义点)在ggplot2 中绘制直线的解决方法?

提前感谢您提供任何建议或(部分)潜在解决方案!

【问题讨论】:

  • 我想我会尝试为此使用 sf 包。
  • 您已经定义了一个围绕原点的五角形。但是由五角形最低三个顶点定义的三角形也包括原点,并且明显小于五角形。是什么让五角形比三角形更受欢迎?
  • @Limey:三角形的顶部不是其中一条约束线的一部分。基本上,蓝色多边形的每一边都应该与其中一条红线重叠,或者换句话说,边每一端的两个顶点应该在同一条红线上。这有帮助吗?
  • 是的,它确实。我将发布部分解决方案...

标签: r ggplot2 geometry convex-hull


【解决方案1】:

部分解决方案

通过使用combn 函数,只需简单的代数即可找到所有线的交点:

intersections <- as_tibble(
                   t(combn(df$Line, 2)), 
                   .name_repair=(x) c("Line1", "Line2")
                 ) %>% 
                 left_join(
                   df %>% rename(Intercept1=Intercept, Slope1=Slope),
                   by=c("Line1"="Line")
                 ) %>% 
                 left_join(
                   df %>% rename(Intercept2=Intercept, Slope2=Slope),
                   by=c("Line2"="Line")
                 ) %>% 
                 mutate(
                   X=(Intercept2 - Intercept1)/(Slope1 - Slope2),
                   Y=Slope1 * X + Intercept1,
                   Row=row_number()
                 ) %>%
                 select(-starts_with("I"), -starts_with("S"))
> intersections
# A tibble: 15 × 5
   Line1 Line2       X       Y   Row
   <chr> <chr>   <dbl>   <dbl> <int>
 1 A     B       0.5     3.5       1
 2 A     C       1.91    2.09      2
 3 A     D      13.8    -9.75      3
 4 A     E      45     -41         4
 5 A     F       2.81    1.19      5
 6 B     C       3.93    6.93      6
 7 B     D      -2.81    0.188     7
 8 B     E      -4.44   -1.44      8
 9 B     F      -8.75   -5.75      9
10 C     D       0.333  -1.7      10
11 C     E      -0.781  -4.38     11
12 C     F       1.11    0.167    12
13 D     E     -17.5     9.00     13
14 D     F      -0.833  -1        14
15 E     F      -3.21   -2.43     15

并检查我们是否正确识别了交叉点

intersections %>% 
  ggplot() +
    geom_vline(xintercept = 0) +
    geom_hline(yintercept = 0) +
    geom_abline(data=df, mapping = aes(intercept = Intercept, slope = Slope, colour = Line)) +
    geom_point(aes(x=X, y=Y), colour="green") +
    coord_cartesian(xlim = c(-6, 6), ylim = c(-6, 6))

现在我们可以再次使用combn 来生成这些交点的所有可能组合,并使用chull 获得每个交点的凸包并从那里继续,但这并不有效。

正如@Roland 建议的那样,sf 包可能是从这里开始的方式,但我对它不是很熟悉。从这里开始,我在大声思考...

我们可以得到这些大小为m 的点的所有子集的凸包(出于显而易见的原因,使用m &gt; 3

library(sf)

getPolygons <- function(data, m=3) { 
  pointSets <- as_tibble(
                 t(combn(1:nrow(intersections), m=m)), 
                 .name_repair=(x) as.character(1:length(x))
               ) %>% 
               mutate(Polygon=row_number()) %>% 
               pivot_longer(
                 -Polygon, 
                 names_to="index", 
                 values_to="Row"
               ) %>% 
               select(-index)
    pointSets %>% 
      group_by(Polygon) %>% 
      group_map(
        function(.x, .y) {
          z <- .x %>% left_join(data, by="Row") %>% select(X, Y)
          st_convex_hull(st_multipoint(as.matrix(z)))
        }
    ) 
allPolygons <- intersections %>% getPolygons(3)

然后检查凸包是否包含原点(并计算那些凸包的面积)

areasOfPolygonsAroundOrigin <- 
  sapply(
    allPolygons,
    function(x) {
      if(!is_empty(st_contains(x, st_point(c(0, 0)))[[1]])) {
        st_area(x) 
      } else {
        Inf
      }
    }
  )

which.min(areasOfPolygonsAroundOrigin)
[1] 311
areasOfPolygonsAroundOrigin[which.min(areasOfPolygonsAroundOrigin)]
[1] 1.465085

如果最小区域是无限的,那么就没有包含原点的凸包,所以我们逐步增加一组包含多个交点的凸包。

逻辑的缺失部分是识别哪些凸包是由连接交叉点的输入线段定义的。那是我无法做到的。 sf_linestring 在这里可能会有所帮助。

【讨论】:

  • 非常感谢,这已经很有帮助了!我可以轻松地将交叉点上的答案扩展到更大的数据集(这比简化的示例要复杂一些)。我肯定会查看sf 包,我不知道它的存在,但它似乎掌握了答案——不过可能有点学习曲线。如果我能找到最后一块拼图,我会回来的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-17
  • 2013-03-03
  • 1970-01-01
  • 2018-01-19
  • 1970-01-01
  • 1970-01-01
  • 2014-02-15
相关资源
最近更新 更多