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

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

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

Я хотел бы заменить значения 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 e.g:

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 ответов


может быть более эффективный способ сделать это с помощью функций 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)

первая функция может выполнять либо прямое, либо обратное заполнение, заменяя каждый NaN ближайшим не-NaN.

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

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

наконец, стоит отметить, что функция может возвращать 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]