Перечислите Понимания, чтобы создать попарное различие - PullRequest
0 голосов
/ 27 ноября 2018

Я не знаком со списочными понятиями, но хотел бы вычислить различие Брей-Кертиса с использованием списочных представлений.Различие дается

def bray(x):
    bray_diss = np.zeros((x.shape[0], x.shape[0]))
    for i in range(0, bray_diss.shape[0]):
        bray_diss[i,i] = 0
        for j in range(i+1, bray_diss.shape[0]):
            l1_diff = abs(x[i,:] - x[j,:])
            l1_sum = x[i,:] + x[j,:] + 1
            bray_diss[i,j] = l1_diff.sum() / l1_sum.sum()
            bray_diss[j,i] = bray_diss[i,j]
    return bray_diss

Я пробовал что-то вроде:

def bray(x):
    [[((abs(x[i,:] - x[j,:])).sum() / (x[i,:] + x[j,:] + 1).sum()) for j in range(0, x.shape[0])] for i in range(0, x.shape[0])]

без успеха, и я не могу понять, что не так!Более того, в первой реализации второй цикл не выполняется для всех значений строки матрицы, чтобы сэкономить время вычислений, как это можно сделать с пониманием списка?

Спасибо!

1 Ответ

0 голосов
/ 13 февраля 2019

Вы ничего не получите с пониманием списка ... кроме лучшего понимания понимания списка!Что вы должны понять, так это то, что понимание списка - это функциональная концепция.Я не буду вдаваться в детали функционального программирования, но вы должны иметь в виду, что функциональное программирование запрещает побочные эффекты.Пример:

my_matrix = np.zeros(n, n)
for i in range(n):
    for j in range(n):
        my_matrix[i,j] = value_of_cell(i,j)

Последняя строка является побочным эффектом: вы изменяете состояние my_matrix.Напротив, бесплатная версия с побочными эффектами будет делать:

np.array([[value_of_cell(i,j) for j in range(n)] for i in range(n)])

У вас нет последовательности «создать-затем-назначить»: вы создаете матрицу, объявляя значения в каждой позиции.Точнее, чтобы создать матрицу:

  • , вы должны объявить значение для каждой ячейки;
  • , когда вам дается пара (i,j), вы не можете использовать ее дляобъявите значение другой ячейки (например, (j,i))

(Если вам нужно преобразовать матрицу позже, вам придется воссоздать ее. Вот почему этот метод может быть дорогим - во времени и пространстве.)

Теперь взгляните на свой код.Когда вы пишете понимание списка, хорошее правило - использовать вспомогательные функции, поскольку они помогают очистить код (здесь мы не пытаемся создать однострочную строку):

def bray(x):
    n = x.shape[0] # cleaner than to repeat x.shape[0] everywhere
    def diss(i,j): # I hope it's correct
        l1_diff = abs(x[i,:] - x[j,:])
        l1_sum = x[i,:] + x[j,:] + 1
        return l1_diff.sum() / l1_sum.sum()

    bray_diss = np.zeros((n, n))
    for i in range(n): # range(n) = range(0,n)
        # bray_diss[i,i] = 0 <-- you don't need to set it to zero here
        for j in range(i+1, n):
            bray_diss[i,j] = diss(i,j)
            bray_diss[j,i] = bray_diss[i,j]
    return bray_diss

Это чище,Каким будет следующий шаг?В приведенном выше коде вы выбираете итерацию по j, которая больше i, и установку двух значений одновременно.Но в понимании списка вы не выбираете ячейки: понимание списка дает вам, для каждой ячейки, координаты, и вы должны объявить значения.

Во-первых, давайте попробуем установить только одно значение дляитерации, то есть использовать два цикла:

def bray(x):
    ...

    bray_diss = np.zeros((n, n))
    for i in range(n):
        for j in range(i+1, n):
            bray_diss[i,j] = inner(i,j)

    for i in range(n):
        for j in range(i):
            bray_diss[i,j] = bray_diss[j,i]

    return bray_diss

Это лучше.Во-вторых, нам нужно присвоить значение каждой ячейке матрицы, а не просто заполнить нулями и выбрать ячейки, которые мы не хотим обновлять:

def bray(x):
    ...

    bray_diss = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            if j>i: # j in range(i+1, n)
                bray_diss[i,j] = inner(i,j) # top right corner
            else # j in range(i+1)
                bray_diss[i,j] = 0. # zeroes in the bottom left corner + diagonal

    for i in range(n):
        for j in range(n):
            if j<i: # j in range(i)
                bray_diss[i,j] = bray_diss[j,i] # fill the bottom left corner now
            else # j in range(i, n)
                bray_diss[i,j] = bray_diss[i,j] # top right corner + diagonal is already ok

    return bray_diss

Короткая версия будетбыть, используя «поддельный троичный условный оператор» Python:

def bray(x):
    ...

    bray_diss = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            bray_diss[i,j] = inner(i,j) if j>i else 0.

    for i in range(n):
        for j in range(n):
            bray_diss[i,j] = bray_diss[j,i] if j<i else bray_diss[i,j] 

    return bray_diss

Теперь мы можем превратить это в список пониманий:

def bray(x):
    ...

    bray_diss_top_right = np.array([[diss(i,j) if j>i else 0. for j in range(n)] for i in range(n)])
    bray_diss = np.array([[bray_diss_top_right[j,i] if j<i else bray_diss_top_right[i,j] for j in range(n)] for i in range(n)])
    return bray_diss

И, если я не ошибаюсь, этоеще проще, как это (окончательная версия):

def bray(x):
    n = x.shape[0]
    def diss(i,j):
        l1_diff = abs(x[i,:] - x[j,:])
        l1_sum = x[i,:] + x[j,:] + 1
        return l1_diff.sum() / l1_sum.sum()

    bray_diss_top_right = np.array([[diss(i,j) if j>i else 0. for j in range(n)] for i in range(n)])
    return bray_diss_top_right + bray_diss_top_right.transpose()

Обратите внимание, что эта версия на , вероятно, (я не измерял) медленнее, чем ваша, но способ построения матрицыНа мой взгляд, легче понять.

...