Я использую Python и имею массив со значениями 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 и np.nan в качестве NoData.

Я хочу заполнить все "нан" значением. Это значение должно составлять большинство окружающих значений.

Например:

1 1 1 1 1
1 n 1 2 2
1 3 3 2 1
1 3 2 3 1

«n» представляет «nan» в этом примере. Большинство его соседей имеют значение 1. Таким образом, «nan» заменяется значением 1.

Обратите внимание, что отверстия, состоящие из «нан», могут иметь размер от 1 до 5. Например (максимальный размер 5 нан):

1 1 1 1 1
1 n n n 2
1 n n 2 1
1 3 2 3 1

Здесь отверстие «нан» имеет следующие окружающие значения:

surrounding_values = [1,1,1,1,1,2,1,2,3,2,3,1,1,1] -> Majority = 1

Я попробовал следующий код:

from sklearn.preprocessing import Imputer

array = np.array(.......)   #consisting of 1.0-6.0 & np.nan
imp = Imputer(strategy="most_frequent")
fill = imp.fit_transform(array)

Это работает довольно хорошо. Однако он использует только одну ось (0 = столбец, 1 = строка). По умолчанию 0 (столбец), поэтому он использует большинство окружающих значений того же столбца. Например:

Array
2 1 2 1 1
2 n 2 2 2
2 1 2 2 1
1 3 2 3 1

Filled Array
2 1 2 1 1
2 1 2 2 2
2 1 2 2 1
1 3 2 3 1

Итак, здесь вы видите, хотя большинство равно 2, большинство окружающих значений столбца равно 1, и поэтому оно становится 1 вместо 2.

В результате мне нужно найти другой метод с использованием Python. Есть предложения или идеи?


ДОПОЛНЕНИЕ :

Здесь вы видите результат, после того как я добавил очень полезное улучшение Мартина Вульгара.

enter image description here

Думайте о «0» как о море (синий), а о других значениях (> 0) как о земле (красный).

Если есть «маленькое» море, окруженное сушей (море может снова иметь размер 1-5 px), оно получит сушу, как вы можете успешно увидеть на полученном изображении. Если окруженное море больше 5px или за пределами суши, море не получит земли (это не видно на изображении, потому что это не так).

Если есть 1px «nan» с большей частью моря, чем суши, он все равно станет сушей (в этом примере это 50/50).

Следующая картинка показывает, что мне нужно. На границе между морем (значение = 0) и сушей (значение> 0) «нан» -пиксель должен получить значение большинства значений земли.

enter image description here

Это звучит сложно, и я надеюсь, что смогу объяснить это живо.

7
Johannes-R-Schmid 9 Янв 2017 в 18:08

3 ответа

Лучший ответ

Возможное решение с использованием {{X0} } и {{ X1}} из scipy.ndimage:

import numpy as np
from scipy.ndimage import label, binary_dilation
from collections import Counter

def impute(arr):
    imputed_array = np.copy(arr)

    mask = np.isnan(arr)
    labels, count = label(mask)
    for idx in range(1, count + 1):
        hole = labels == idx
        surrounding_values = arr[binary_dilation(hole) & ~hole]
        most_frequent = Counter(surrounding_values).most_common(1)[0][0]
        imputed_array[hole] = most_frequent

    return imputed_array

РЕДАКТИРОВАТЬ: Что касается вашего вопроса, связанного со свободными вопросами, вы можете расширить приведенный выше код, чтобы добиться того, что вам нужно:

import numpy as np
from scipy.ndimage import label, binary_dilation, binary_closing

def fill_land(arr):
    output = np.copy(arr)

    # Fill NaN-s
    mask = np.isnan(arr)
    labels, count = label(mask)
    for idx in range(1, count + 1):
        hole = labels == idx
        surrounding_values = arr[binary_dilation(hole) & ~hole]
        output[hole] = any(surrounding_values)

    # Fill lakes
    land = output.astype(bool)
    lakes = binary_closing(land) & ~land
    labels, count = label(lakes)
    for idx in range(1, count + 1):
        lake = labels == idx
        output[lake] = lake.sum() < 6

    return output
2
Martin Valgur 9 Янв 2017 в 17:02

После невероятной помощи Мартина Валгура у меня есть результат, который мне нужен.

Поэтому я добавил следующие строки в код Martins:

from scipy.ndimage import label, binary_dilation
from scipy.stats import mode

def impute(arr):
    imputed_array = np.copy(arr)

    mask = np.isnan(arr)
    labels, count = label(mask)
    for idx in range(1, count + 1):
        hole = labels == idx
        surrounding_values = arr[binary_dilation(hole) & ~hole]

        sv_list = np.ndarray.tolist(surrounding_values) #!
        for sv in sv_list:  #!
            if sv == 0:
                sv_list.remove(sv)
        surrounding_values = np.array(sv_list)

        imputed_array[hole] = mode(surrounding_values).mode[0]

    return imputed_array
0
Johannes-R-Schmid 9 Янв 2017 в 17:36

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

import numpy as np
from collections import Counter


def getModulusSurround(data):

    tempdata = list(filter(lambda x: x, data))
    c = Counter(tempdata)
    if c.most_common(1)[0][0]:
        return(c.most_common(1)[0][0])


def main():

    array = [[1, 2, 2, 4, 5],
             [2, 3, 4, 5, 6],
             [3, 4, None, 6, 7],
             [1, 4, 2, 3, 4],
             [4, 6, 2, 2, 4]]

    array = np.array(array)

    for i in range(5):
        for j in range(5):
            if array[i,j] == None:

                temparray = array[i-1:i+2,j-1:j+2]
                array[i,j] = getModulusSurround(temparray.flatten())

    print(array)

main()
1
Po Stevanus Andrianta 9 Янв 2017 в 15:54