Что является более эффективным способом обработки массивов с пустым фрагментом на основе нескольких критериев? - PullRequest
3 голосов
/ 01 марта 2012

Я написал некоторый код, который для диапазона лет (например, 15 лет), ndimage.filters.convolve используется для свертывания массива (например, массив1), тогда где результирующий массив (например, массив2) выше случайным образомсгенерированное число, другому массиву (например, массиву 3) присваивается значение 1. Как только массиву 3 было присвоено значение, равное одному, он подсчитывает каждый год, и когда он в конечном итоге достигает определенного значения (например, 5), массив1 обновляется.в этом месте.

Извините, если это немного сбивает с толку.Я действительно заставил скрипт работать, используя numpy.where(boolean expression, value, value), но там, где мне нужно было несколько выражений (например, where array2 == 1 and array3 == 0), я использовал цикл for, чтобы перебирать каждое значение в массивах.Это прекрасно работает в приведенном здесь примере, но когда я заменяю массивы большими массивами (полный скрипт импортирует ГИС-сетки и преобразует их в массивы), цикл for обрабатывается несколько минут каждый год.Поскольку нам приходится запускать модель более 60 лет 1000 раз, мне нужно найти гораздо более эффективный способ обработки этих массивов.

Я пытался использовать несколько выражений в numpy.where, но не смог работатькак заставить его работать.Я также попытался с помощью zip (array) объединить массивы вместе, но я не смог их обновить, я думаю, потому что это создало кортежи элементов массива.

Я приложил копию скрипта, как уже упоминалось ранее, он работает именно так, как мне нужно.Однако для этого нужно сделать это более эффективно.Если у кого-то есть предложения, это было бы здорово.Это мой первый пост о питоне, поэтому я все еще считаю себя новичком.

import numpy as np
from scipy import ndimage
import random
from pylab import *

###################### FUNCTIONS ###########################

def convolveArray1(array1, kern1):

    newArray = ndimage.filters.convolve(array1, kern1, mode='constant')

    return newArray


######################## MAIN ##############################

## Set the number of years
nYears = range(1,16)

## Cretae array1
array1 = np.zeros((10,10), dtype=np.int) # vegThreshMask

# Add some values to array1
array1[[4,4],[4,5]] = 8
array1[5,4] = 8
array1[5,5] = 8

## Create kerna; array
kernal = np.ones((3,3), dtype=np.float32)

## Create an empty array to be used as counter
array3 = np.zeros((10,10), dtype=np.int)

## iterate through nYears
for y, yea in enumerate(nYears):

    # Create a random number for the year
    randNum = randint(7, 40)
    print 'The random number for year %i is %i' % (yea, randNum)
    print

    # Call the convolveArray function
    convArray = convolveArray1(array1, kernal)

    # Update array2 where it is greater than the random number    
    array2 = np.where(convArray > randNum, 1, 0)
    print 'Where convArray > randNum in year %i' % (yea)
    print array2
    print 

    # Iterate through array2 
    for a, ar in enumerate(array2):
        for b, arr in enumerate(ar):
            if all(arr == 1 and array3[a][b] == 0):
                array3[a][b] = 1
            else:
                if array3[a][b] > 0:
                    array3[a][b] = array3[a][b] + 1
            if array3[a][b] == 5:
                array1[a][b] = 8

    # Remove the initial array (array1) from the updated array3   
    array3 = np.where(array1 > 0, 0, array3)
    print 'New array3 after %i years' % (yea)
    print '(Excluding initial array)'
    print array3
    print    

print 'The final output of the initial array'
print array1

Ответы [ 2 ]

3 голосов
/ 01 марта 2012

Я подозреваю, что вы можете значительно ускориться, если начнете использовать вещание.Например, начиная с вашей строки # Iterate through array2 мы можем удалить явный цикл и просто передать переменные, которые мы хотим изменить.Примечание. Я использую AX вместо arrayX для ясности:

# Iterate through A2

idx  = (A2==1) & (A3==0)
idx2 = (~idx)  & (A3>0)
A3[idx ]  = 1
A3[idx2] += 1
A1[A3==5] = 8

Кроме того, это значительно улучшает ясность кода, как только вы привыкнете к этому стилю, поскольку вы явно не работаете с индексами(ваши a и b здесь).

Стоит ли беспокоиться?

Я попросил OP выполнить тест скорости после попытки кода выше:

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

После тестирования отклик значительно увеличился в 40 раз!При работе с большими массивами смежных данных, в которых выполняются простые маски, numpy - гораздо лучшая альтернатива спискам нативных питонов.

1 голос
/ 01 марта 2012

Звучит так, как будто вы пытались использовать несколько условий в np.where, используя выражения типа array1 > 0 and array2 < 0. Это не работает из-за того, как логические операции работают в Python, как описано здесь . Сначала оценивается array1 > 0, затем он преобразуется в логическое значение с использованием метода __nonzero__ (переименованного в __bool__ в Python 3). Не существует единственного полезного способа преобразования массива в логическое значение, и в настоящее время нет способа переопределить поведение логических операторов (хотя я считаю, что это обсуждается в будущих версиях), поэтому в numpy, ndarray.__nonzero__ определяется, чтобы вызвать исключение. Вместо этого вы можете использовать np.logical_and, np.logical_or и np.logical_not, поведение которых вы ожидаете.

Я не знаю, насколько это ускорит вас. Если вы в конечном итоге выполняете множество операций индексации массивов в циклах, возможно, стоит взглянуть на cython , с помощью которого вы можете легко ускорить операции с массивами, переместив их в расширение C.

...