2016-11-05 9 views
1

Я хотел бы заполнить значения N/A в DataFrame выборочно. В частности, если в столбце есть последовательность последовательных nans, я хочу, чтобы они были заполнены предыдущим значением non-nan, но только если длина наной последовательности ниже заданного порога. Например, если пороговое значение равно 3, то последовательность внутри столбца 3 или менее будет заполнена предшествующим значением non-nan, тогда как последовательность из 4 или более nans будет оставлена ​​как есть.Использование fillna() выборочно в pandas

То есть, если входной DataFrame является

2 5 4 
    nan nan nan 
    nan nan nan 
    5 nan nan 
    9 3 nan 
    7 9 1 

Я хочу выход быть:

2 5 4 
    2 5 nan 
    2 5 nan 
    5 5 nan 
    9 3 nan 
    7 9 1 

fillna функция, когда применяется к DataFrame, имеет метод и предельные параметры. Но этого, к сожалению, недостаточно, чтобы выполнить задачу. Я попытался указать method='ffill' и limit=3, но это заполняет первые 3 последовательности любой последовательности, не выборочно, как описано выше.

Я предполагаю, что это может быть закодировано путем перехода столбца за столбцом с некоторыми условными операторами, но я подозреваю, что должно быть что-то большее, чем Pythonic. Любые рекомендации на эффективном пути для этого?

ответ

3

Работая со смежными группами, все еще немного неудобно в пандах .. или, по крайней мере, я не знаю, сколький способ сделать это, что совсем не то же самое. :-)

Один из способов получить то, что вы хотите, будет использовать шаблон сравнения-cumsum-GroupBy:

In [68]: nulls = df.isnull() 
    ...: groups = (nulls != nulls.shift()).cumsum() 
    ...: to_fill = groups.apply(lambda x: x.groupby(x).transform(len) <= 3) 
    ...: df.where(~to_fill, df.ffill()) 
    ...: 
Out[68]: 
    0 1 2 
0 2.0 5.0 4.0 
1 2.0 5.0 NaN 
2 2.0 5.0 NaN 
3 5.0 5.0 NaN 
4 9.0 3.0 NaN 
5 7.0 9.0 1.0 

Ладно, еще одна альтернатива, которая мне не нравится, потому что это слишком сложно :

def method_2(df): 
    nulls = df.isnull() 
    filled = df.ffill(limit=3) 
    unfilled = nulls & (~filled.notnull()) 
    nf = nulls.replace({False: 2.0, True: np.nan}) 
    do_not_fill = nf.combine_first(unfilled.replace(False, np.nan)).bfill() == 1 
    return df.where(do_not_fill, df.ffill()) 

Это не использует groupby инструменты и так должно быть быстрее. Обратите внимание, что другой подход заключается в том, чтобы вручную (используя сдвиги) определить, какие элементы должны быть заполнены, потому что они представляют собой группу длиной 1, 2 или 3.

+0

Спасибо @DSM. Это очень приятное решение, которое действительно дает нам то, что мы ищем. Всего один комментарий: он довольно медленный. Я использовал его с пределом 3 для DataFrame размером 530x11500, и это заняло около 32 секунд. Таким образом, хотя это решение велико, альтернативное решение, которое снижает задействованное время работы, будет высоко оценено – splinter

+0

Очень быстро! Время стены: 9,01 с – splinter

Смежные вопросы