Разделение списка на основе других значений списка в 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.

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