Разделение списка на основе других значений списка в Mathematica - PullRequest
3 голосов
/ 23 апреля 2010

В 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];

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

Ответы [ 6 ]

5 голосов
/ 23 апреля 2010

Вот краткий способ сделать это, используя новую функцию 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]]]
5 голосов
/ 23 апреля 2010

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

4 голосов
/ 23 апреля 2010

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

points[[
    Flatten[Position[clusterIndices, #]]
    ]] & /@
 Union[clusterIndices]
1 голос
/ 23 апреля 2010

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

Transpose[{clusterIndices, points}]

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

1 голос
/ 23 апреля 2010

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

Map[#[[1]] &, GatherBy[Thread[{points, clusterIndices}], #[[2]] &], {2}]
1 голос
/ 23 апреля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...