Заполнить значения NaN средним значением предыдущих строк?

#python #pandas #nan #mean #fillna

#python #панды #nan #среднее #заполнение

Вопрос:

Я должен заполнить значения nan столбца в фрейме данных средним значением предыдущих 3 экземпляров. Вот следующий пример:

 df = pd.DataFrame({'col1': [1, 3, 4, 5, np.NaN, np.NaN, np.NaN, 7]})
df
col1
0   1.0
1   3.0
2   4.0
3   5.0
4   NaN
5   NaN
6   NaN 
7   7.0
 

И вот результат, который мне нужен:

 col1
0   1.0
1   3.0
2   4.0
3   5.0
4   4.0
5   4.3
6   4.4 
7   7.0
 

Я попробовал pd.rolling, но он работает не так, как я хочу, когда столбец имеет более одного значения NaN в рулоне:

 df.fillna(df.rolling(3, min_periods=1).mean().shift())


col1
0   1.0
1   3.0
2   4.0
3   5.0
4   4.0 # np.nanmean([3, 4, 5])
5   4.5 # np.nanmean([np.NaN, 4, 5])
6   5.0 # np.nanmean([np.NaN, np.naN ,5])
7   7.0
 

Может кто-нибудь помочь мне с этим? Заранее спасибо!

Ответ №1:

Вероятно, не самый эффективный, но краткий и выполняет свою работу

 from functools import reduce
reduce(lambda d, _: d.fillna(d.rolling(3, min_periods=3).mean().shift()), range(df['col1'].isna().sum()), df)
 

вывод

 
    col1
0   1.000000
1   3.000000
2   4.000000
3   5.000000
4   4.000000
5   4.333333
6   4.444444
7   7.000000
 

мы в основном используем fillna , но требуем min_periods=3 значения, которое будет заполнять только один NaN за раз, или, скорее, те NAN, которые имеют три номера, отличные от NaN, непосредственно предшествующих ему. Затем мы reduce повторяем эту операцию столько раз, сколько NAN в col1

Ответ №2:

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

Циклический подход

Для каждой строки в dataframe получите значение из col1 . Затем возьмите среднее значение последних строк. (В этом списке может быть меньше 3, если мы находимся в начале фрейма данных.) Если значение равно NaN, замените его средним значением. Затем сохраните значение обратно в dataframe. Если список значений из последних строк содержит более 3 значений, удалите последнее.

 def impute(df2, col_name):
    last_3 = []
    for index in df.index:
        val = df2.loc[index, col_name]
        if len(last_3) > 0:
            imputed = np.nanmean(last_3)
        else:
            imputed = None
        if np.isnan(val):
            val = imputed
        last_3.append(val)
        df2.loc[index, col_name] = val
        if len(last_3) > 3:
            last_3.pop(0)
 

Повторная операция со столбцом

Основная идея здесь заключается в том, чтобы заметить, что в вашем примере pd.rolling первое значение замены NA является правильным. Итак, вы применяете скользящее среднее значение, берете первое значение NA для каждого прогона значений NA и используете это число. Если вы применяете это повторно, вы заполняете первое пропущенное значение, затем второе пропущенное значение, затем третье. Вам нужно будет запустить этот цикл столько раз, сколько самая длинная серия последовательных значений NA.

 def impute(df2, col_name):
    while df2[col_name].isna().any().any():
        # If there are multiple NA values in a row, identify just
        # the first one
        first_na = df2[col_name].isna().diff() amp; df2[col_name].isna()
        # Compute mean of previous 3 values
        imputed = df2.rolling(3, min_periods=1).mean().shift()[col_name]
        # Replace NA values with mean if they are very first NA
        # value in run of NA values
        df2.loc[first_na, col_name] = imputed
 

Сравнение производительности

Запустив оба из них в 80000-строчном фрейме данных, я получаю следующие результаты:

 Loop approach takes 20.744 seconds
Repeated column operation takes 0.056 seconds
 

Комментарии:

1. Большое вам спасибо!! Это мне очень помогло!!