Аналог scipy's отмечен_comprehension "вдоль оси" - PullRequest
1 голос
/ 13 марта 2020

Если у вас есть одномерный массив numpy points длины n и одномерный массив с k уникальными метками одинаковой формы, scipy.ndimage.measurements.labeled_comprehension позволяет применять общую функцию f к элементам, соответствующим каждой метке, без выполнения k проходов по всему массиву. Это полезно, когда количество меток велико относительно n и f принимает массив flat .

Однако что делать, если вы хотите применить функцию f который работает на неплоский массив? В моем случае я пытался рассчитать диаметр каждого кластера в наборе двумерных точек, аннотированных кластером. Вот настройка:

import numpy as np
from scipy import spatial
from scipy.ndimage import labeled_comprehension


# set up fake data
np.random.seed(0)
n_centroids = 3
centroids = np.random.rand(n_centroids, 2)
cluster_sizes = np.random.randint(3, 5, size=n_centroids)
# labels for each point for clusters ranging from 1 to n_centroids inclusive
labels = np.repeat(np.arange(n_centroids), cluster_sizes) + 1
# random points around each centroid
points = np.zeros((cluster_sizes.sum(), 2))
points[:,0] = np.repeat(centroids[:,0], cluster_sizes)
points[:,1] = np.repeat(centroids[:,1], cluster_sizes)
points += 0.05 * np.random.randn(cluster_sizes.sum(), 2)

# black box function; we can assume that
# pts.shape[0] is variable
# pts.shape[1:] is fixed
def diameter(pts):
  # print(pts)
  # need at least 3 points to construct the convex hull
  if pts.shape[0] <= 1:
    return 0
  if pts.shape[0] == 2:
    return ((pts[0] - pts[1])**2).sum()
  # two points which are fruthest apart will occur as vertices of the convex hull
  hull = spatial.ConvexHull(pts)
  candidates = pts[spatial.ConvexHull(pts).vertices]
  return spatial.distance_matrix(candidates, candidates).max()

Моя попытка применить labelled_comprehension была следующей:

labeled_comprehension(points, np.stack((labels, labels), 1), None, diameter, np.float, None)

В результате произошла следующая трассировка:

 ***debug printout of argument to diameter***
 [0.54606175 0.70982384 0.61708686 0.71030458 0.42751578 0.69253658
 0.57922483 0.59353398 0.53885592 0.61675172 0.59887815 0.59936469
 0.60759051 0.61581654 0.48206846 0.69325341 0.47792915 0.76500534
 0.40335361 0.65921638]

---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-79-4c2d0fdb5c38> in <module>()
----> 1 labeled_comprehension(points, np.stack((labels, labels), 1), None, diameter, np.float, None)

1 frames

<ipython-input-77-99537dd58821> in diameter(pts)
     25     return ((pts[0] - pts[1])**2).sum()
     26   # two points which are fruthest apart will occur as vertices of the convex hull
---> 27   hull = spatial.ConvexHull(pts)
     28   candidates = pts[spatial.ConvexHull(pts).vertices]
     29   return spatial.distance_matrix(candidates, candidates).max()

qhull.pyx in scipy.spatial.qhull.ConvexHull.__init__()

IndexError: tuple index out of range

Как мы видим, что diameter получил плоский массив в качестве входных данных.

Я могу достичь желаемого результата, многократно маскируя каждую метку, например,

def desired_output(points, labels, f=diameter):
  return np.array([f(points[labels==i]) for i in np.unique(labels)])

# [0.1904019410968485, 0.06874095082563453, 0.12943266211372922]

Однако для этого требуется k ( количество кластеров) проходит над n элементами (точками).

Вместо этого я хотел бы сделать что-то вроде этого:

def desired_output2(points, labels, f=diameter):
  return labelled_comprehension_along_axis(points, labels, f=diameter, axis=-1)

Вопрос : как можно один реализует векторизованную версию labelled_comprehension, которая принимает функцию черного ящика f, которая работает с массивом формы (k, const) и возвращает скаляр, не заставляя O(k) проходить через входной массив?

В идеале это можно распространить на случай, когда const - это «множество» измерений, аналогично _over_axes функциям.

...