Python: замена значений в массиве


У меня есть 1-мерный набор данных с некоторыми значениями no data, которые задаются как 9999. Вот выдержка, поскольку она довольно длинная:

this_array = [   4,    4,    1, 9999, 9999, 9999,   -5,   -4, ... ]

Я хотел бы заменить значения no data средними значениями ближайших значений с обеих сторон, однако, поскольку некоторые значения no data имеют самые близкие значения, а также значения no data, замена их немного сложнее. то есть я хотел бы, чтобы три значения no data были заменены на -2. Я создал цикл, чтобы пройти через каждый из скаляров в массиве и тест на отсутствие данных:

for k in this_array:
    if k == 9999:
        temp = np.where(k == 9999, (abs(this_array[k-1]-this_array[k+1])/2), this_array[k])
    else:
        pass
this_array[k] = temp

Однако мне нужно добавить в функцию if или способ принять значение до k-1 или после k+1, если это также равно 9999, например:

if np.logical_or(k+1 == 9999, k-1 == 9999):
    temp = np.where(k == 9999, (abs(this_array[k-2]-this_array[k+2])/2), this_array[k])

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

Как и было предложено: если первая и/или последняя точки не являются данными, то они предпочтительно заменить ближайшей точкой данных.

4 6

4 ответа:

Может быть более эффективный способ сделать это с помощью функций numpy, но вот решение, использующее модуль itertools :

from itertools import groupby

for k, g in groupby(range(len(this_array)), lambda i: this_array[i] == 9999):
    if k:
        indices = list(g)
        new_v = (this_array[indices[0]-1] + this_array[indices[-1]+1]) / 2
        this_array[indices[0]:indices[-1]+1].fill(new_v)

Если последний элемент или первый элемент может быть 9999, вы используете следующее:

from itertools import groupby

for k, g in groupby(range(len(this_array)), lambda i: this_array[i] == 9999):
    if k:
        indices = list(g)
        prev_i, next_i = indices[0]-1, indices[-1]+1
        before = this_array[prev_i] if prev_i != -1 else this_array[next_i]
        after = this_array[next_i] if next_i != len(this_array) else before
        this_array[indices[0]:next_i].fill((before + after) / 2)

Пример использования второй версии:

>>> from itertools import groupby
>>> this_array = np.array([9999, 4, 1, 9999, 9999, 9999, -5, -4, 9999])
>>> for k, g in groupby(range(len(this_array)), lambda i: this_array[i] == 9999):
...     if k:
...         indices = list(g)
...         prev_i, next_i = indices[0]-1, indices[-1]+1
...         before = this_array[prev_i] if prev_i != -1 else this_array[next_i]
...         after = this_array[next_i] if next_i != len(this_array) else before
...         this_array[indices[0]:next_i].fill((before + after) / 2)
...
>>> this_array
array([ 4,  4,  1, -2, -2, -2, -5, -4, -4])

Я бы сделал что-нибудь примерно следующее:

import numpy as np

def fill(arr, fwd_fill):
  out = arr.copy()
  if fwd_fill:
    start, end, step = 0, len(out), 1
  else:
    start, end, step = len(out)-1, -1, -1
  cur = out[start]
  for i in range(start, end, step):
    if np.isnan(out[i]):
      out[i] = cur
    else:
      cur = out[i]
  return out

def avg(arr):
  fwd = fill(arr, True)
  back = fill(arr, False)
  return (fwd[:-2] + back[2:]) / 2.

arr = np.array([   4,    4,    1, np.nan, np.nan, np.nan,   -5,   -4])
print arr
print avg(arr)
Первая функция может выполнять либо прямое, либо обратное заполнение, заменяя каждый НАН ближайшим не-Наном.

Как только вы это сделаете, вычисление среднего будет тривиальным и выполняется второй функцией.

Вы не говорите, как вы хотите, чтобы первый и последний элемент обрабатывались, поэтому код просто отрубает их.

Наконец, стоит отметить, что функция может возвращать NaNs, Если либо первый, либо последний элемент входного массива отсутствует (в этом случае отсутствуют данные для вычисления некоторых средних значений).

Ладно, боюсь, мне придется написать его самому, вы можете использовать np.interp или эквивалентные (возможно, несколько более приятные и гораздо более функциональные) функции scipy, которые вы можете найти в scipy.interpolate.

Ладно, перечитываю... Полагаю, вам не нужна линейная интерполяция? В этом случае, конечно, это не совсем работает... Хотя я уверен, что есть некоторые векторизованные методы.

imort numpy as np
# data is the given array.
data = data.astype(float) # I cast to float, if you don't want that badly...
valid = data != 9999
x = np.nonzero(valid)[0]
replace = np.nonzero(~valid)[0]
valid_data = data[x]

# using np.interp, but I think you will find better things in scipy.interpolate
# if you don't mind using scipy.
data[replace] = np.interp(replace, x, valid_data,
                                   left=valid_data[0], right=valid_data[-1])

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

def a(list, first, depth):    
  if ([] == list):
    return []
  car = list[0]
  cdr = list[1:]
  if (9999 ==  car):        
      return a(cdr, first, depth+1)
  if (depth != 0):
      avg = [((first + car) /2)] * depth
      return  avg + [car] + a(cdr, car, 0)
  else:
      return [car] + a(cdr, car, 0)



print a([1,2,9999, 4, 9999,9999, 12],0,0)
# => [1, 2, 3, 4, 8, 8, 12]