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

EDIT: Решение, с которым я столкнулся:

pointIndices =
  Map[#[[2]] &,
    GatherBy[MapIndexed[{#1, #2[[1]]} &, clusterIndices], First],
    {2}];
pointsByCluster = Map[Part[points, #] &, pointIndices];

Это лучший способ сделать это?

Ответ 1

Как насчет этого?

points[[
    Flatten[Position[clusterIndices, #]]
    ]] & /@
 Union[clusterIndices]

Ответ 2

Как отметили @High Performance Mark и @Nicholas Wilson, я начал бы комбинировать два списка вместе через Transpose или Thread. В этом случае

In[1]:= Transpose[{clusterIndices, points}]==Thread[{clusterIndices, points}]
Out[1]:= True

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

@High Performance Mark дает хорошее представление о 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 удалили много полезных примеров, и это было одним из них.

Ответ 3

Вот краткий способ сделать это, используя новую функцию SplitBy в версии 7.0, которая должна быть довольно быстрой:

SplitBy[Transpose[{points, clusterIndices}], Last][[All, All, 1]]

Если вы не используете 7.0, вы можете реализовать это как:

Split[Transpose[{points, clusterIndices}], Last[#]==Last[#2]& ][[All, All, 1]]

Update

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

FindClusters[Thread[Rule[clusterIndices, points]]]

Ответ 4

Я не знаю о "лучше", но более обычным способом в функциональных языках было бы не добавлять индексы для обозначения каждого элемента (вашего 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.

Ответ 5

Если я подумаю о чем-то более простом, я добавлю к сообщению.

Map[#[[1]] &, GatherBy[Thread[{points, clusterIndices}], #[[2]] &], {2}]

Ответ 6

Моим первым шагом было бы выполнить

Transpose[{clusterIndices, points}]

и мой следующий шаг будет зависеть от того, что вы хотите с этим сделать; Select приходит в голову.