Вычислить попарное расстояние в scipy с пропущенными значениями

Я немного озадачен тем, как _1 _ обрабатывает отсутствующие (nan) значения.

Так что на случай, если я испортил размеры своей матрицы, давайте разберемся с этим. Из документов:

Точки расположены как m n-мерных векторов-строк в матрице X.

Итак, давайте сгенерируем три точки в 10-мерном пространстве с пропущенными значениями:

numpy.random.seed(123456789)
data = numpy.random.rand(3, 10) * 5
data[data < 1.0] = numpy.nan

Если я вычислю евклидово расстояние этих трех наблюдений:

pdist(data, "euclidean")

Я получил:

array([ nan,  nan,  nan])

Однако, если я отфильтрую все столбцы с пропущенными значениями, я получу правильные значения расстояния:

valid = [i for (i, col) in enumerate(data.T) if ~numpy.isnan(col).any()]
pdist(data[:, valid], "euclidean")

Я получил:

array([ 3.35518662,  2.35481185,  3.10323893])

Таким образом, я выбрасываю больше данных, чем хотелось бы, поскольку мне не нужно фильтровать всю матрицу, а только пары векторов, сравниваемых за раз. Могу ли я каким-то образом заставить pdist или аналогичную функцию выполнять попарное маскирование?


Изменить:

Поскольку моя полная матрица довольно велика, я провел некоторые временные тесты на небольшом наборе данных, представленном здесь.

1.) Scipy функция.

%timeit pdist(data, "euclidean")
10000 loops, best of 3: 24.4 µs per loop

2.) К сожалению, предоставленное решение примерно в 10 раз медленнее.

%timeit numpy.array([pdist(data[s][:, ~numpy.isnan(data[s]).any(axis=0)], "euclidean") for s in map(list, itertools.combinations(range(data.shape[0]), 2))]).ravel()
1000 loops, best of 3: 231 µs per loop

3.) Затем я сделал тест «чистого» Python и был приятно удивлен:

from scipy.linalg import norm

%%timeit
m = data.shape[0]
dm = numpy.zeros(m * (m - 1) // 2, dtype=float)
mask = numpy.isfinite(data)
k = 0
for i in range(m - 1):
    for j in range(i + 1, m):
        curr = numpy.logical_and(mask[i], mask[j])
        u = data[i][curr]
        v = data[j][curr]
        dm[k] = norm(u - v)
        k += 1
10000 loops, best of 3: 98.9 µs per loop

Итак, я думаю, что путь вперед - это Cythonize приведенный выше код в функции.


person Midnighter    schedule 16.07.2014    source источник
comment
Если вы цитонизируете это, вы, вероятно, не будете выглядеть лучше, создавая его непосредственно вокруг замаскированных массивов? Также будьте осторожны с переносом производительности малых выборок на большие выборки ...   -  person deinonychusaur    schedule 17.07.2014
comment
Извините за мой худший комментарий. Запуск на 1000 точек в 100 тусклом пространстве по-прежнему ускоряет решение Python (2,65x).   -  person deinonychusaur    schedule 17.07.2014
comment
@deinonychusaur Что вы имеете в виду, когда строите его непосредственно на основе масок? Вам все равно нужно использовать маску, предоставленную ma.array, если вы хотите скрыть данные, верно? Может быть, вы могли бы отредактировать свой ответ или мы немного поговорим.   -  person Midnighter    schedule 17.07.2014
comment
Именно так, но питон-код будет выглядеть аккуратнее. Я покопался в github.com/scipy /scipy/blob/v0.13.0/scipy/spatial/src/ тоже, не знаю, поможет ли это. Мог бы поболтать, но не знаю, могу ли я что-то еще предложить.   -  person deinonychusaur    schedule 17.07.2014
comment
@Midnighter, удалось ли вам ускорить работу над кодом?   -  person Gökhan Sever    schedule 13.04.2016
comment
Я думаю, что еще один способ ускорить это сравнение - использовать модуль многопроцессорности и позволить различным ядрам работать с разными фрагментами данных.   -  person Gökhan Sever    schedule 13.04.2016


Ответы (2)


На самом деле вам может быть лучше с этим готовым решением: https://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.nan_euclidean_distances.html

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

person George Pligoropoulos    schedule 01.08.2020
comment
Это действительно было бы отличным решением, но оно было добавлено только в декабре 2019 года, а этому вопросу уже несколько лет. Я приму ваш ответ, потому что это функция, которую я бы использовал сегодня. - person Midnighter; 02.08.2020

Если я вас правильно понял, вам нужно расстояние для всех измерений, для которых два вектора имеют допустимые значения.

К сожалению, pdist не понимает маскированные массивы в этом смысле, поэтому я изменил ваше полурешение, чтобы не уменьшать информацию. Однако это не самое эффективное решение и не самое читаемое:

np.array([pdist(data[s][:, ~numpy.isnan(data[s]).any(axis=0)], "euclidean") for s in map(list, itertools.combinations(range(data.shape[0]), 2))]).ravel()

Внешний преобразователь в массив и ravel просто для того, чтобы придать ему форму, соответствующую тому, что вы ожидаете.

itertools.combinations производит все попарно возможные индексы массива data.

Затем я просто нарезаю на них данные (для правильного нарезания должно быть list, а не tuple) и выполняю попарную фильтрацию nan, как это делал ваш код.

person deinonychusaur    schedule 16.07.2014
comment
Спасибо за ваш ответ. См. Мой отредактированный вопрос выше. - person Midnighter; 17.07.2014