Я пытаюсь (но безуспешно) построить отдельные точки данных в столбце 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"))
Удаление линий между уровнями кругов может быть достигнуто с помощью 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, хотя аккорды также присутствуют.
Ищем другой генератор (x−h)^2+(y−k)^2+(z−l)^2=r^2 , поскольку выбор rep(..., 360) не завершает круг(ы) , дополнительным преимуществом которого, скорее всего, будет отсутствие необходимости проходить процесс «удаления», поскольку точки данных расположены и равномерно распределены, как в сфере,
Спасибо за вашу постоянную поддержку этого вопроса. Как бы вы внедрили новый генератор в код?
Ниже я использовал другой расчет и ошибся в вопросе недостаточной выборки, но @gregor-thomas предлагает очень хороший circle_xy , который дополнительно устраняет последний пробел, где r = df$prop. Я поиграюсь с этим.





Не уверен, что это хоть сколько-нибудь близко к тому, чего вы хотели бы достичь, но я могу получить 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
хорошая работа, но не на основе моих данных
Да, извините, с этим не получилось :(
Используя ваши данные и исходное решение, это просто стирание без учета того, что происходит в исходном решении, по сути, механическая и повторяющаяся операция, дополнительно используя 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), и создается впечатление, что Зенон выступает против прибытия. Итак, осталось еще несколько образцов.
Интересный. Как вы определили строки, которые нужно удалить?
В данные добавлен идентификатор 1:nrow. Подмножество z, построенное какploty_ly, так иplot(data_c12$theta). Гуэристика n-1, по-видимому, развивалась на основе расстояния между стабилизацией начальных тета-результатов. Я задокументирую выше и попытаюсь удалить «конец» после того, как график пересекает z[x][1], вероятно, используя geos::touches. В противном случае придется много раз просматривать сюжеты, итеративно...
Ух ты! Впечатляющий! почему n = 4896?
И еще 12 и друзья (7,3) не закрыты. Сколько точек, добавленных к вписанному квадрату, дает воспринимаемый круг (по разрешению дисплея)? d с приведенным выше chull можно переместить в круг_xyz, уменьшив сигму, скажем, 0,000001, тогда как круг_xyz использует равное расстояние между точками, поэтому дополнения к n уменьшают расстояние между точками (где резвится Зенон). Но летящая стрела попадает в цель, вопреки аргументу Зенона, что стрела никогда не достигает цели, в точке n+1. Поэтому мне любопытно, как получить n + 1, а не мое «добавить еще n» выше. Звучит как вопрос для ТАК, не так ли?
Незамкнутые круги меня не волнуют. Но, опять же, как вы пришли к n = 4896? Вы говорите: «n кратно max(z)». max(z) переводится как length(df$position), верно? Но как вы определили количество кратных?
Если бы у меня был здравый смысл, я бы использовал 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.
Используя оценки 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 все еще открыть. Согласованный?
Извините, я не могу за вами следить. Можете ли вы попытаться ответить на мой вопрос о том, как вы пришли к определению n = 4896, более простыми словами? Или, в идеале, предоставить решение, которое вообще не требует жесткого кодирования n.
data_12 <- data[data$z == 12, ], dplyr::consecutive_id() вернул последовательные идентификаторы, но не проверил их на «близость», которую можно было бы отфильтровать по евклидову расстоянию, чтобы найти «длинные пересекающиеся линии» и удалить.