Если у вас есть одномерный массив 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
функциям.