Разделение списка на основе других значений списка в Mathematica
В Mathematica у меня есть список координат точек
size = 50;
points = Table[{RandomInteger[{0, size}], RandomInteger[{0, size}]}, {i, 1, n}];
и список индексов кластера, к которым эти точки принадлежат
clusterIndices = {1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1};
Какой самый простой способ разбить точки на два отдельных списка на основе значений clusterIndices?
РЕДАКТИРОВАТЬ: Решение, которое я придумал:
pointIndices =
Map[#[[2]] &,
GatherBy[MapIndexed[{#1, #2[[1]]} &, clusterIndices], First],
{2}];
pointsByCluster = Map[Part[points, #] &, pointIndices];
Есть ли лучший способ сделать это?
6 ответов
Как насчет этого?
points[[
Flatten[Position[clusterIndices, #]]
]] & /@
Union[clusterIndices]
Как сказали @High Performance Mark и @Nicholas Wilson, я бы начал с объединения двух списков с помощью Transpose
или же Thread
, В этом случае,
In[1]:= Transpose[{clusterIndices, points}]==Thread[{clusterIndices, points}]
Out[1]:= True
В какой-то момент я посмотрел на который был быстрее, и я думаю, Thread
незначительно быстрее. Но это действительно имеет значение, когда вы используете очень длинные списки.
@ Высокая производительность Марк делает хороший совет, предлагая Select
, Но это позволит вам вытащить только один кластер за раз. Код для выбора кластера 1 выглядит следующим образом:
Select[Transpose[{clusterIndices, points}], #[[1]]==1& ][[All, All, 2]]
Поскольку вы, кажется, хотите создать все кластеры, я предлагаю сделать следующее:
GatherBy[Transpose[{clusterIndices, points}], #[[1]]& ][[All, All, 2]]
который имеет преимущество быть одним вкладышем, и единственная сложная часть заключалась в выборе правильного Part
результирующего списка. Хитрость в определении, сколько All
условия необходимы это отметить, что
Transpose[{clusterIndices, points}][[All,2]]
требуется, чтобы получить очки обратно из транспонированного списка. Но "кластерный" список имеет один дополнительный уровень, следовательно, второй All
,
Следует отметить, что второй параметр в GatherBy
это функция, которая принимает один параметр, и ее можно заменить любой функцией, которую вы хотите использовать. Таким образом, это очень полезно. Однако, если вы хотите трансформировать ваши данные в процессе их сбора, я бы посмотрел на Reap
а также Sow
,
Редактировать: Reap
а также Sow
несколько используются, и довольно мощный. Они несколько сбивают с толку, но я подозреваю, GatherBy
реализуется с использованием их внутри. Например,
Reap[ Sow[#[[2]], #[[1]] ]& /@ Transpose[{clusterIndices, points}], _, #2& ]
делает то же самое, что и мой предыдущий код, без лишних усилий по смещению индексов с точек. По существу, Sow
помечает каждую точку своим индексом, затем Reap собирает все метки (_
для второго параметра) и выводит только точки. Лично я использую это вместо GatherBy, и я закодировал это в функцию, которую я загружаю, следующим образом:
SelectEquivalents[x_List,f_:Identity, g_:Identity, h_:(#2&)]:=
Reap[Sow[g[#],{f[#]}]&/@x, _, h][[2]];
Примечание: этот код является измененной формой того, что было в файлах справки в 5.x. Но файлы справки 6.0 и 7.0 удалили много полезных примеров, и это был один из них.
Вот краткий способ сделать это с помощью нового SplitBy
функция в версии 7.0, которая должна быть довольно быстрой:
SplitBy[Transpose[{points, clusterIndices}], Last][[All, All, 1]]
Если вы не используете 7.0, вы можете реализовать это следующим образом:
Split[Transpose[{points, clusterIndices}], Last[#]==Last[#2]& ][[All, All, 1]]
Обновить
Извините, я не видел, чтобы вы хотели только две группы, которые я считаю кластеризованными, а не разделенными. Вот код для этого:
FindClusters[Thread[Rule[clusterIndices, points]]]
Если я подумаю о чем-то более простом, я добавлю к сообщению.
Map[#[[1]] &, GatherBy[Thread[{points, clusterIndices}], #[[2]] &], {2}]
Моим первым шагом было бы выполнить
Transpose[{clusterIndices, points}]
и мой следующий шаг будет зависеть от того, что вы хотите сделать с этим; Select
приходит на ум.
Я не знаю, что такое "лучше", но более обычным способом в функциональных языках было бы не добавлять индексы для маркировки каждого элемента (вашего MapIndexed), а вместо этого просто бегать по каждому списку:
Map[#1[[2]] &,
Sort[GatherBy[
Thread[ {#1, #2} &[clusterIndices, points]],
#1[[1]] &], #1[[1]][[1]] < #2[[1]][[1]] &], {2}]
Большинство людей, воспитанных в Lisp/ML/etc, напишут Thread
мгновенное функционирование - это способ реализовать идеи zip из этих языков.
Я добавил в Sort
потому что, похоже, ваша реализация столкнется с проблемами, если clusterIndices = {2[...,2],1,...}
, С другой стороны, мне все еще нужно было бы добавить в строку, чтобы исправить проблему, что если clusterIndices имеет 3, но не 2, выходные индексы будут неправильными. Из вашего фрагмента не ясно, как вы собираетесь получать данные.
Я считаю, что вам будет гораздо проще обрабатывать списки, если вы освежитесь в своем хобби-проекте, таком как создание простого CAS на языке наподобие Haskell, где синтаксис гораздо больше подходит для функциональной обработки списков, чем Mathematica.