Преобразуйте и нарисуйте отдельные сгруппированные значения в (почти) полные круги в «графической» трехмерной спирали

Я пытаюсь (но безуспешно) построить отдельные точки данных в столбце prop в виде полных кругов (если смотреть сверху) в plotly 3D-спирали (см. данные ниже). Моя идея заключалась в том, чтобы repсъесть значения prop на 10, чтобы иметь достаточно точек, чтобы нарисовать круг, но полученный график далек от того, что мне хотелось бы получить - трехмерная спираль, где каждое prop значение сгруппировано по size и position представлен в виде полного круга. Как этого можно достичь?

data <- df %>%
  group_by(size) %>%
  slice(rep(row_number(), 10)) %>% 
  arrange(size, position) %>% 
  group_by(size, position) %>%
  mutate(
    radius = prop,
    theta = 2 * pi * position/row_number(),
    x = radius * sin(theta),
    y = radius * cos(theta)
  ) %>%
  ungroup() %>%
  mutate(z = row_number())

library(plotly)
plot_ly(data, x = ~x, y = ~y, z = ~z,
        type = 'scatter3d', 
        mode = 'lines',
        line = list(width = 5, color = ~size,
                    colorscale = list(c(0,'#0000FF'), c(0.5, "#00FF00"), c(1,'#FF0000'))))

Обновлено:

У меня есть хотя бы начальное решение:

data <- df %>%
  group_by(size) %>%
  slice(rep(row_number(), 360)) %>%   # larger number of duplicated rows
  arrange(size, position) %>% 
  group_by(size, position) %>%
  mutate(row = row_number(),
         radius = prop,
         theta = 2 * pi * position/row,
         x = radius * sin(theta),
         y = radius * cos(theta)
  ) %>%
  ungroup() %>%
  mutate(z = consecutive_id(position))  # different conceptualization of z

И результат гораздо ближе к тому, что я имею в виду:

Что все еще неоптимально, так это (i) линии, соединяющие разные уровни z, и (ii) (любопытные) соединяющие линии на плоскостях круга. Как можно удалить линии такого типа, сделать их невидимыми или превратить в пунктирные?

Данные:

df <- structure(list(size = c(3L, 3L, 3L, 4L, 4L, 4L, 4L, 5L, 5L, 5L, 
                              5L, 5L), position = c(1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5), prop = c(0.0926574818826153, 
                                                                                                  0.110336863900613, 0.30584534522577, 0.0632319256702261, 0.0857179727070362, 
                                                                                                  0.0963964254605284, 0.269251863083524, 0.0500172581505538, 0.0706956603595971, 
                                                                                                  0.0864162665913584, 0.102858577300825, 0.288838683360005)), row.names = c(NA, 
                                                                                                                                                                            -12L), class = c("tbl_df", "tbl", "data.frame"))
data_12 <- data[data$z == 12, ], dplyr::consecutive_id() вернул последовательные идентификаторы, но не проверил их на «близость», которую можно было бы отфильтровать по евклидову расстоянию, чтобы найти «длинные пересекающиеся линии» и удалить.
Chris 22.04.2024 18:03

Удаление линий между уровнями кругов может быть достигнуто с помощью plot_ly(..., split = factor(data$z), d_dist <- dist(data[, 7:8], method = 'euclidean')[cumsum(1:(nrow(data)-1))]; data$dist[2:4320] <- d_dist может быть длиннее, чем линии в стиле knn, хотя аккорды также присутствуют.

Chris 23.04.2024 22:21

Ищем другой генератор (x−h)^2+(y−k)^2+(z−l)^2=r^2 , поскольку выбор rep(..., 360) не завершает круг(ы) , дополнительным преимуществом которого, скорее всего, будет отсутствие необходимости проходить процесс «удаления», поскольку точки данных расположены и равномерно распределены, как в сфере,

Chris 26.04.2024 04:23

Спасибо за вашу постоянную поддержку этого вопроса. Как бы вы внедрили новый генератор в код?

Chris Ruehlemann 27.04.2024 15:06

Ниже я использовал другой расчет и ошибся в вопросе недостаточной выборки, но @gregor-thomas предлагает очень хороший circle_xy , который дополнительно устраняет последний пробел, где r = df$prop. Я поиграюсь с этим.

Chris 27.04.2024 16:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Не уверен, что это хоть сколько-нибудь близко к тому, чего вы хотели бы достичь, но я могу получить 3 отдельные спирали, адаптировав идею пружины, описанную здесь.

split = ~z в вызове plot_ly() избегал соединительных линий.

library(tidyverse)
library(plotly)

create_spiral <- \(x, y, xend, yend, diameter = 1, tension = 0.75, n = 50) {
  length <- sqrt((x - xend)^2 + (y - yend)^2)
  n_revolutions <- length / (diameter * tension)
  n_points <- n * n_revolutions
  radians <- seq(0, n_revolutions * 2 * pi, length.out = n_points)
  x <- seq(x, xend, length.out = n_points)
  y <- seq(y, yend, length.out = n_points)
  data.frame(
    x = cos(radians) * diameter / 2 + x,
    y = sin(radians) * diameter / 2 + y
  )
}

spirals <- map(1:3, \(z) {
  create_spiral(
    x = 4, y = 2, xend = 8, yend = 6,
    diameter = 4, tension = 0.6, n = 50
  ) |>
    mutate(z = z * 5)
}) |>
  bind_rows()

plot_ly(spirals,
  x = ~x, y = ~y, z = ~z, split = ~z,
  type = "scatter3d",
  mode = "lines"
)

Created on 2024-04-22 with reprex v2.1.0

хорошая работа, но не на основе моих данных

Chris Ruehlemann 22.04.2024 15:26

Да, извините, с этим не получилось :(

Carl 22.04.2024 15:37
Ответ принят как подходящий

Используя ваши данные и исходное решение, это просто стирание без учета того, что происходит в исходном решении, по сути, механическая и повторяющаяся операция, дополнительно используя split для удаления соединительных линий между кругами:

remove_rows
 [1]  721  722 1441 1801 1802 2161 2162 2163 3241 3242 3601 3602 3603 3961 3962
[16] 3963 3964 

data5 <- data4[-remove_rows, ]
plot_ly(data5, x = ~x, y = ~y, z = ~z, split = factor(data5$z), 
        type = 'scatter3d', 
        mode = 'lines',
        line = list(width = 5, color = ~size,
                    colorscale = list(c(0,'#0000FF'), c(0.5, "#00FF00"), c(1,'#FF0000'))))

Цветовые различия между кругами теряются.

Удаление и последующий split скрывают порядок сюжета, поскольку график по заданному z начинается на соединительной линии от предшествующего z.

Уровни 1,2,4,8,9 не требовали удаления, чтобы получить характерную форму.

seq(1, 4320, 360) # for start rows by `z[x]` in data
 [1]    1  361  721 1081 1441 1801 2161 2521 2881 3241 3601 3961
plot(data$theta)
abline(h = 7, col = 'red')
abline(h = 6, col = 'black')

Пики соответствуют индексу начала уровней круга, z[x][1], уровни 1–12 слева направо, количество точек над «красной» линией соответствует количеству точек, удаленных для достижения вышеуказанного графика, использование split на data$z для удаления соединительных линий между кругами .

Анализ сюжета и его связь с количеством точек, которые нужно удалить, были получены постфактум, поэтому я применил более эмпирический подход, просматривая множество графиков и спрашивая: «Это выглядит правильно?» Как было сказано выше, уровни 1,2,4,8,9 не требовали удаления, в результате чего были удалены строки.

# data$z[1-12] starting rows 
[1]    1  361  721 1081 1441 1801 2161 2521 2881 3241 3601 3961
remove_rows
 [1]  721  722 1441 1801 1802 2161 2162 2163 3241 3242 3601 3602 3603 3961 3962
[16] 3963 3964

Теперь, если вы не выполнили тест «использовать мои данные», ваш подход основан на выборках тета. Используя подход, который я нашел, link которого я включу, когда найду снова:

circles <- function(n, mu, sigma) {
    lr <- Map(rlnorm, n = n, meanlog = mu, sdlog = sigma)
    N <- length(lr)
    n <- lengths(lr, FALSE)
    data.frame(group = rep.int(gl(N, 1L), n),
               r = unlist(lr, FALSE, FALSE),
               theta = runif (sum(n), 0, 2 * pi))
} 
d <- circles(n = rep(c(500, 750, 1000),3), mu = rep(log(c(1,2,4)),3), sigma = rep(c(0.00001, 0.00001, 0.00001),3))
d$x = d$r * sin(d$theta)
d$y = d$r * cos(d$theta)
d$z = as.numeric(as.character(d$group))
plot_ly(d, x = ~x, y = ~y, z = ~z, split = factor(d$z),
type = 'scatter3d', 
        mode = 'lines',
        line = list(width = 5))

Крысиное гнездо из пересекающихся хорд, из-за которых они выглядят как диски, можно упростить, обведя кольцами по периметру (без пробелов), используя chull, а затемplot_ly.

Адаптация circle_xy для размещения z

circle_xyz <- function(n, r, z, close_loop = FALSE) {
theta = seq(0, 2 * pi, length.out = n + 1)
if (!close_loop) theta = theta[-(n +1)]
cbind(x = r * cos(theta), y = r * sin(theta), z = z)
}
too_many_circles <- circle_xyz(n = 4896, r = df$prop, z = 1:12, close_loop = TRUE)
Warning messages:
1: In r * cos(theta) :
  longer object length is not a multiple of shorter object length
2: In r * sin(theta) :
  longer object length is not a multiple of shorter object length
3: In cbind(x = r * cos(theta), y = r * sin(theta), z = z) :
  number of rows of result is not a multiple of vector length (arg 3)
 too_many_df = as.data.frame(too_many_circles)
plot_ly(too_many_df, x = ~x, y = ~y, z = ~z, split = factor(too_many_df$z),
 type = 'scatter3d',
 mode = 'lines', 
 line = list(width = 5))

У нас самые большие круги почти замкнуты, n кратно max(z), и создается впечатление, что Зенон выступает против прибытия. Итак, осталось еще несколько образцов.

Интересный. Как вы определили строки, которые нужно удалить?

Chris Ruehlemann 24.04.2024 09:19

В данные добавлен идентификатор 1:nrow. Подмножество z, построенное какploty_ly, так иplot(data_c12$theta). Гуэристика n-1, по-видимому, развивалась на основе расстояния между стабилизацией начальных тета-результатов. Я задокументирую выше и попытаюсь удалить «конец» после того, как график пересекает z[x][1], вероятно, используя geos::touches. В противном случае придется много раз просматривать сюжеты, итеративно...

Chris 24.04.2024 16:30

Ух ты! Впечатляющий! почему n = 4896?

Chris Ruehlemann 28.04.2024 15:48

И еще 12 и друзья (7,3) не закрыты. Сколько точек, добавленных к вписанному квадрату, дает воспринимаемый круг (по разрешению дисплея)? d с приведенным выше chull можно переместить в круг_xyz, уменьшив сигму, скажем, 0,000001, тогда как круг_xyz использует равное расстояние между точками, поэтому дополнения к n уменьшают расстояние между точками (где резвится Зенон). Но летящая стрела попадает в цель, вопреки аргументу Зенона, что стрела никогда не достигает цели, в точке n+1. Поэтому мне любопытно, как получить n + 1, а не мое «добавить еще n» выше. Звучит как вопрос для ТАК, не так ли?

Chris 28.04.2024 18:52

Незамкнутые круги меня не волнуют. Но, опять же, как вы пришли к n = 4896? Вы говорите: «n кратно max(z)». max(z) переводится как length(df$position), верно? Но как вы определили количество кратных?

Chris Ruehlemann 29.04.2024 11:12

Если бы у меня был здравый смысл, я бы использовал nrow(d) sigma 0,000001 = 6750 в качестве начальной оценки для Circle_xyz(n. 3 & 7 закрывается на 3*(nrow(d)) = 20250, а остается 12 open, и 4 * nrow(d) = 27000, и все (наконец) закрыты. Итак, мой n должен был быть где-то между 20250 ~ 27000, а 4896 было далеко не так, но это отражается на результатахplot_ly. выскажу еще несколько мыслей о том, что лучше всего(n) закрыть max(df$prop) , где geos может вступить в игру. Но мне, вероятно, придется спросить о SO.

Chris 29.04.2024 19:19

Используя оценки d и свойство теты n+1 в close_loop = TRUE в круге_xyz и это предупреждение...circle_xyz(n = seq(20250, 27000, 1), r = sort(df$prop, decreasing = TRUE)[1], z = 12, close_loop = TRUE) Warning message: In seq.default(0, 2 * pi, length.out = n + 1) : first element used of 'length.out' argument, т. е. n 20250, nrow(df)20251 возвращается из круга_xyz и 12 закрыто, если n было 20249, 12 все еще открыть. Согласованный?

Chris 29.04.2024 20:08

Извините, я не могу за вами следить. Можете ли вы попытаться ответить на мой вопрос о том, как вы пришли к определению n = 4896, более простыми словами? Или, в идеале, предоставить решение, которое вообще не требует жесткого кодирования n.

Chris Ruehlemann 30.04.2024 13:11

Другие вопросы по теме