2010-09-07 4 views
10

Ich arbeite mit einem 2D Numpy masked_array in Python. Ich muss die Datenwerte im maskierten Bereich so ändern, dass sie dem nächsten unmaskierten Wert entsprechen.Füllen Sie fehlende Werte mit dem nächsten Nachbarn in Python numpy masked arrays?

NB. Wenn es mehr als einen nächsten unmaskierten Wert gibt, kann er einen dieser nächsten Werte annehmen (von denen einer am einfachsten zu codieren ist).

z.B.

import numpy 
import numpy.ma as ma 

a = numpy.arange(100).reshape(10,10) 
fill_value=-99 
a[2:4,3:8] = fill_value 
a[8,8] = fill_value 
a = ma.masked_array(a,a==fill_value) 

>>> a [[0 1 2 3 4 5 6 7 8 9] 
    [10 11 12 13 14 15 16 17 18 19] 
    [20 21 22 -- -- -- -- -- 28 29] 
    [30 31 32 -- -- -- -- -- 38 39] 
    [40 41 42 43 44 45 46 47 48 49] 
    [50 51 52 53 54 55 56 57 58 59] 
    [60 61 62 63 64 65 66 67 68 69] 
    [70 71 72 73 74 75 76 77 78 79] 
    [80 81 82 83 84 85 86 87 -- 89] 
    [90 91 92 93 94 95 96 97 98 99]], 
  • Ich brauche es wie folgt aussehen:
>>> a.data 
[[0 1 2 3 4 5 6 7 8 9] 
[10 11 12 13 14 15 16 17 18 19] 
[20 21 22 ? 14 15 16 ? 28 29] 
[30 31 32 ? 44 45 46 ? 38 39] 
[40 41 42 43 44 45 46 47 48 49] 
[50 51 52 53 54 55 56 57 58 59] 
[60 61 62 63 64 65 66 67 68 69] 
[70 71 72 73 74 75 76 77 78 79] 
[80 81 82 83 84 85 86 87 ? 89] 
[90 91 92 93 94 95 96 97 98 99]], 

NB. woher "?" könnte einen der benachbarten unmaskierten Werte annehmen.

Was ist der effizienteste Weg, dies zu tun?

Danke für Ihre Hilfe.

Antwort

9

Sie np.roll verschobenen Kopien von a zu machen verwenden könnte, dann Booleschen Logik auf den Masken die Spots zu identifizieren, die ausgefüllt werden:

import numpy as np 
import numpy.ma as ma 

a = np.arange(100).reshape(10,10) 
fill_value=-99 
a[2:4,3:8] = fill_value 
a[8,8] = fill_value 
a = ma.masked_array(a,a==fill_value) 
print(a) 

# [[0 1 2 3 4 5 6 7 8 9] 
# [10 11 12 13 14 15 16 17 18 19] 
# [20 21 22 -- -- -- -- -- 28 29] 
# [30 31 32 -- -- -- -- -- 38 39] 
# [40 41 42 43 44 45 46 47 48 49] 
# [50 51 52 53 54 55 56 57 58 59] 
# [60 61 62 63 64 65 66 67 68 69] 
# [70 71 72 73 74 75 76 77 78 79] 
# [80 81 82 83 84 85 86 87 -- 89] 
# [90 91 92 93 94 95 96 97 98 99]] 

for shift in (-1,1): 
    for axis in (0,1):   
     a_shifted=np.roll(a,shift=shift,axis=axis) 
     idx=~a_shifted.mask * a.mask 
     a[idx]=a_shifted[idx] 

print(a) 

# [[0 1 2 3 4 5 6 7 8 9] 
# [10 11 12 13 14 15 16 17 18 19] 
# [20 21 22 13 14 15 16 28 28 29] 
# [30 31 32 43 44 45 46 47 38 39] 
# [40 41 42 43 44 45 46 47 48 49] 
# [50 51 52 53 54 55 56 57 58 59] 
# [60 61 62 63 64 65 66 67 68 69] 
# [70 71 72 73 74 75 76 77 78 79] 
# [80 81 82 83 84 85 86 87 98 89] 
# [90 91 92 93 94 95 96 97 98 99]] 

Wenn Sie eine größere verwenden möchten Satz der nächsten Nachbarn, könnte man vielleicht so etwas tun:

neighbors=((0,1),(0,-1),(1,0),(-1,0),(1,1),(-1,1),(1,-1),(-1,-1), 
      (0,2),(0,-2),(2,0),(-2,0)) 

Beachten Sie, dass die Reihenfolge der Elemente in neighbors wichtig ist. Wahrscheinlich möchten Sie fehlende Werte mit dem nächsten Nachbarn und nicht nur mit einem Nachbarn ausfüllen. Es gibt wahrscheinlich eine klügere Möglichkeit, die Nachbarn-Sequenz zu erzeugen, aber ich sehe es momentan nicht.

a_copy=a.copy() 
for hor_shift,vert_shift in neighbors: 
    if not np.any(a.mask): break 
    a_shifted=np.roll(a_copy,shift=hor_shift,axis=1) 
    a_shifted=np.roll(a_shifted,shift=vert_shift,axis=0) 
    idx=~a_shifted.mask*a.mask 
    a[idx]=a_shifted[idx] 

anzumerken, dass np.roll glücklich die Unterkante nach oben rollt, so dass ein fehlender Wert an der Spitze kann durch einen Wert von ganz unten ausgefüllt werden. Wenn das ein Problem ist, muss ich mehr darüber nachdenken, wie ich es beheben kann. Die offensichtliche, aber nicht sehr clevere Lösung wäre if Aussagen zu verwenden und die Kanten eine andere Reihenfolge der zulässigen Nachbarn zu füttern ...

+0

Great! Das funktioniert für meine Zwecke. Eine Frage - könnte es verallgemeinert werden, um für größere Datenlücken zu arbeiten, wo der nächste unmaskierte Wert mehr als einen Punkt entfernt ist? –

+0

@Pete - Ein schneller Weg, dies zu tun, besteht darin, die for-Schleifen in ein 'while np.any (a.mask):' einzufügen. @unutbu - Verflucht übrigens übrigens die Interpolation der nächsten Nachbarn! –

+0

Danke Joe! Komplimente von dir machen mich sehr glücklich. :) – unutbu

5

Für kompliziertere Fälle könnten Sie scipy.spatial:

from scipy.spatial import KDTree 
x,y=np.mgrid[0:a.shape[0],0:a.shape[1]] 

xygood = np.array((x[~a.mask],y[~a.mask])).T 
xybad = np.array((x[a.mask],y[a.mask])).T 

a[a.mask] = a[~a.mask][KDTree(xygood).query(xybad)[1]] 

print a 
    [[0 1 2 3 4 5 6 7 8 9] 
    [10 11 12 13 14 15 16 17 18 19] 
    [20 21 22 13 14 15 16 17 28 29] 
    [30 31 32 32 44 45 46 38 38 39] 
    [40 41 42 43 44 45 46 47 48 49] 
    [50 51 52 53 54 55 56 57 58 59] 
    [60 61 62 63 64 65 66 67 68 69] 
    [70 71 72 73 74 75 76 77 78 79] 
    [80 81 82 83 84 85 86 87 78 89] 
    [90 91 92 93 94 95 96 97 98 99]] 
+0

Könnte dieser Ansatz für die Extrapolation außerhalb der konvexen Hülle verwendet werden, nachdem einige unregelmäßig beabstandete Daten mit einem Nearest Neighbor-Algorithmus interpoliert wurden? Es scheint, dass es funktionieren könnte, aber vielleicht gibt es bessere Alternativen. Ich frage mich nur, Danke. – SSZero

5

ich in der Regel Verwenden Sie eine Abstandstransformation, wie von Juh_ in this question weise vorgeschlagen.

Dies gilt nicht direkt für maskierte Arrays, aber ich denke nicht, dass es dort so schwer zu transponieren ist, und es ist ziemlich effizient, ich hatte kein Problem, es auf große 100MPix Bilder anzuwenden.

dort die entsprechende Methode Kopieren Referenz:

import numpy as np 
from scipy import ndimage as nd 

def fill(data, invalid=None): 
    """ 
    Replace the value of invalid 'data' cells (indicated by 'invalid') 
    by the value of the nearest valid data cell 

    Input: 
     data: numpy array of any dimension 
     invalid: a binary array of same shape as 'data'. True cells set where data 
       value should be replaced. 
       If None (default), use: invalid = np.isnan(data) 

    Output: 
     Return a filled array. 
    """ 
    #import numpy as np 
    #import scipy.ndimage as nd 

    if invalid is None: invalid = np.isnan(data) 

    ind = nd.distance_transform_edt(invalid, return_distances=False, return_indices=True) 
    return data[tuple(ind)] 
+0

Sehr einfache Lösung, danke! –

Verwandte Themen