Работа с пропущенными значениями в Python Pandas

Раздел: Продвинутый Python -> Pandas

Эффективное заполнение пропусков: комбинированный метод и заполнение по группам

Наиболее универсальный подход сочетает несколько методов: сначала применяется forward fill для заполнения последующими значениями, затем backward fill для начальных пропусков, а для оставшихся числовых столбцов - заполнение медианой или средним по группе. Это сохраняет временную структуру и статистику.


import pandas as pd
import numpy as np

df = pd.DataFrame({
    'date': pd.date_range('2023-01-01', periods=6, freq='D'),
    'value': [10, np.nan, 12, np.nan, 14, np.nan],
    'group': ['A', 'A', 'B', 'B', 'A', 'B']
})
print('Исходные данные:')
print(df)
  

обработка больших данных python (обработка больших данных в python)

        date  value group
0 2023-01-01   10.0     A
1 2023-01-02    NaN     A
2 2023-01-03   12.0     B
3 2023-01-04    NaN     B
4 2023-01-05   14.0     A
5 2023-01-06    NaN     B
  

очистка данных python (очистка данных в python)


# Комбинированное заполнение с учетом группы
df['value_filled'] = df.groupby('group')['value'] \
    .transform(lambda x: x.fillna(method='ffill').fillna(method='bfill'))
# Для оставшихся NaN заполняем медианой по группе
df['value_filled'] = df['value_filled'].fillna(
    df.groupby('group')['value'].transform('median')
)
print(df)
  

Python подготовка данных (подготовка данных в python)

        date  value group  value_filled
0 2023-01-01   10.0     A          10.0
1 2023-01-02    NaN     A          10.0
2 2023-01-03   12.0     B          12.0
3 2023-01-04    NaN     B          12.0
4 2023-01-05   14.0     A          14.0
5 2023-01-06    NaN     B          12.0
  

работа с dataframe python (работа с dataframe в python)

Потенциальные трудности:

Если группа содержит только NaN, transform('median') вернет NaN. В этом случае требуется глобальная медиана или константа. Также нужно следить за типом данных: после fillna может измениться тип столбца (float).

Как полностью удалить строки с пропусками?

Метод dropna() удаляет строки или столбцы, содержащие хотя бы одно пропущенное значение. Это самый простой способ, но он ведёт к потере данных.


df_clean = df.dropna()  # по умолчанию axis=0
print('Удалены строки с любым NaN:')
print(df_clean)
  

Python работа с большими данными (работа с большими данными в python)

        date  value group
0 2023-01-01   10.0     A
2 2023-01-03   12.0     B
4 2023-01-05   14.0     A
  

структурированные данные python (структурированные данные в python)

Параметр how='all' удаляет только строки, где все значения NaN. Параметр thresh задаёт минимальное количество не-NaN значений для сохранения строки.


# Оставляем строки, где хотя бы 2 не-NaN значения
df_thresh = df.dropna(thresh=2)
print('С порогом 2:')
print(df_thresh)
  

генерация данных python (генерация данных в python)

        date  value group
0 2023-01-01   10.0     A
1 2023-01-02    NaN     A
2 2023-01-03   12.0     B
3 2023-01-04    NaN     B
4 2023-01-05   14.0     A
5 2023-01-06    NaN     B
  

Python код символа (код символа в python)

Типичная ошибка:

При использовании dropna() без указания оси удаляются строки, что может быть неочевидно, если нужно удалить столбцы. Используйте axis=1 для удаления столбцов.

Как заполнить пропуски средним или медианой?

Заполнение средним (mean) или медианой (median) подходит для числовых данных, не имеющих временной зависимости. Это сохраняет общую статистику, но может сглаживать выбросы.


mean_value = df['value'].mean()
median_value = df['value'].median()
print(f'Среднее: {mean_value:.2f}, Медиана: {median_value:.2f}')

df['value_mean'] = df['value'].fillna(mean_value)
df['value_median'] = df['value'].fillna(median_value)
print(df[['value', 'value_mean', 'value_median']])
  

код из файла python (код из файла python)

Среднее: 12.00, Медиана: 12.00
   value  value_mean  value_median
0   10.0        10.0          10.0
1    NaN        12.0          12.0
2   12.0        12.0          12.0
3    NaN        12.0          12.0
4   14.0        14.0          14.0
5    NaN        12.0          12.0
  

обработка данных на python (обработка данных на python)

Проблемы:

Среднее чувствительно к выбросам, медиана - более устойчива. При наличии категориальных данных среднее не применимо; для них используют моду.

Как использовать интерполяцию для заполнения пропусков?

Метод interpolate() заполняет пропуски, используя линейную или другую интерполяцию между соседними известными значениями. Особенно полезен для временных рядов.


df_interp = df.copy()
df_interp['value'] = df_interp['value'].interpolate(method='linear')
print('После линейной интерполяции:')
print(df_interp)
  

обработка символьных данных python (обработка символьных данных в python)

        date  value group
0 2023-01-01   10.0     A
1 2023-01-02   11.0     A
2 2023-01-03   12.0     B
3 2023-01-04   13.0     B
4 2023-01-05   14.0     A
5 2023-01-06    NaN     B   # последний не интерполирован, так как нет следующего
  

Python преобразование в строку (преобразование в строку в python)

Для интерполяции с учетом дат используется method='time'. Параметр limit ограничивает количество последовательных пропусков для заполнения.


# Интерполяция только если подряд не более 1 пропуска
df_interp_limit = df['value'].interpolate(limit=1)
print(df_interp_limit)
  

Python как проверить строку (проверка строки в python)

Ошибка:

Если пропуски находятся на краях (первый или последний элемент), интерполяция не работает. Используйте limit_direction='both' для расширения на края.

Как использовать SimpleImputer из scikit-learn?

Библиотека sklearn предоставляет класс SimpleImputer для заполнения пропусков средним, медианой, модой или константой. Это удобно в пайплайнах машинного обучения.


from sklearn.impute import SimpleImputer
import numpy as np

X = np.array([[1, 2], [np.nan, 3], [7, 6]])
imputer = SimpleImputer(strategy='mean')
X_imputed = imputer.fit_transform(X)
print(X_imputed)
  

как работать с данными в python (работа с данными в python)

[[1. 2.]
 [4. 3.]
 [7. 6.]]
  

Column values python (значения столбца в python (pandas))

Важно:

SimpleImputer работает с массивами numpy, а не с DataFrame напрямую (можно преобразовать). После impute теряются индексы и названия столбцов. Для Pandas лучше использовать встроенные методы.

Как заполнить пропуски константой или значением из другого столбца?

Метод fillna() позволяет передать константу, словарь (для разных столбцов разные значения) или даже другой Series/DataFrame.


df['value'] = df['value'].fillna(0)  # заполнить нулями
# Заполнить разными значениями для разных столбцов
df.fillna({'value': -1, 'group': 'unknown'}, inplace=True)
# Заполнить из другого столбца (например, предыдущим значением из того же столбца)
df['value'] = df['value'].fillna(df['value'].shift(1))
  

Типичная ошибка:

При заполнении константой искажаются статистические свойства. Если используется inplace=True, оригинальный DataFrame изменяется без возврата.

- столбец dataframe python (работа со столбцом dataframe в pandas)
- Python get url (получение url в python)
- Get data python (получение данных в python)

Расширенные примеры обработки пропусков

1. Интерполяция с полиномом второго порядка и направлением «оба»

Если данные имеют нелинейную зависимость, линейная интерполяция может быть неточной. Параметр method='polynomial' с указанием порядка позволяет построить полиномиальную кривую. Параметр limit_direction='both' распространяет интерполяцию на края.

Пример

import pandas as pd
import numpy as np

df_poly = pd.DataFrame({
    'x': [1, np.nan, np.nan, 4, np.nan, 6],
    'y': [10, np.nan, np.nan, 5, np.nan, 1]
})
# Интерполяция полиномом 2-го порядка с заполнением краев
df_poly_interp = df_poly.interpolate(method='polynomial', order=2, limit_direction='both')
print(df_poly_interp)
  
          x    y
0  1.000000  10.0
1  2.333333   8.0
2  3.666667   6.0
3  4.000000   5.0
4  5.333333   3.0
5  6.000000   1.0
  

Обратите внимание: для полиномиальной интерполяции требуется не менее order+1 известных точек. Если их недостаточно, возникнет ошибка.

Ошибка:

При малом количестве известных значений интерполяция может дать нереалистичные результаты (эффект Рунге). Рекомендуется проверять визуально.

2. Использование KNNImputer для заполнения на основе ближайших соседей

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

Пример

from sklearn.impute import KNNImputer
import pandas as pd
import numpy as np

df_knn = pd.DataFrame({
    'A': [1, 2, np.nan, 4, 5],
    'B': [np.nan, 4, 5, 6, 7],
    'C': [10, 20, 30, np.nan, 50]
})
imputer = KNNImputer(n_neighbors=2)
df_imputed = pd.DataFrame(imputer.fit_transform(df_knn), columns=df_knn.columns)
print(df_imputed)
  
     A    B     C
0  1.0  4.5  10.0
1  2.0  4.0  20.0
2  3.5  5.0  30.0
3  4.0  6.0  40.0
4  5.0  7.0  50.0
  

Здесь для строки 0 столбец B (пропуск) заполнен средним значений из двух ближайших соседей. Параметр weights='distance' учитывает расстояние.

Проблема:

KNNImputer требует масштабирования признаков (стандартизации) из-за евклидова расстояния. Пропуски могут быть в нескольких столбцах, что усложняет расчёт расстояний.

3. Создание индикатора пропусков и условное заполнение

Часто полезно сохранить информацию о том, было ли значение пропущено. Для этого создаётся новый столбец-флаг (1 - пропуск, 0 - нет). Затем можно заполнить пропуски медианой, но только если они не превышают порог.

Пример

df_flag = pd.DataFrame({
    'value': [1, np.nan, 3, np.nan, 5, np.nan],
    'group': ['A', 'A', 'B', 'B', 'A', 'B']
})
# Создаём индикатор
df_flag['was_missing'] = df_flag['value'].isna().astype(int)
# Заполняем медианой только те пропуски, у которых was_missing == 1
median_val = df_flag['value'].median()
df_flag['value_filled'] = df_flag['value'].fillna(median_val)
print(df_flag)
  
   value group  was_missing  value_filled
0    1.0     A            0           1.0
1    NaN     A            1           3.0
2    3.0     B            0           3.0
3    NaN     B            1           3.0
4    5.0     A            0           5.0
5    NaN     B            1           3.0
  

Важно:

Флаг was_missing может стать полезным признаком для моделей машинного обучения, показывая аномалии или неполноту данных.

4. Forward fill с ограничением количества шагов и направлением в обе стороны

Метод fillna(method='ffill', limit=1) позволяет заполнить не более одного пропуска подряд. Если затем применить bfill с тем же лимитом, можно заполнить цепочки пропусков длиной до 2. Параметр limit_area='inside' ограничивает заполнение только внутренними пропусками (не краевыми).

Пример

df_ffill = pd.DataFrame({
    'A': [np.nan, 1, np.nan, np.nan, 2, np.nan]
})
# Сначала forward fill с лимитом 1, затем backward fill с лимитом 1
result = df_ffill['A'].fillna(method='ffill', limit=1) \
                     .fillna(method='bfill', limit=1)
print(result)
  
0    NaN   # первый не затронут
1    1.0
2    1.0   # заполнен ffill (1 шаг)
3    NaN   # не заполнен, так как ffill уже использовал лимит, а bfill не может, т.к. после NaN следующий известный на 2 шага
4    2.0
5    2.0   # заполнен bfill (1 шаг)
Name: A, dtype: float64
  

Таким образом, управление лимитами позволяет избежать чрезмерного распространения значений.

Тонкость:

Лимит применяется к каждому направлению отдельно. После ffill лимит сбрасывается для bfill.

5. Использование numpy.where для условной замены NaN

Иногда требуется заменить NaN на одно значение, если выполнено условие (например, столбец группы), и на другое - в противном случае. numpy.where позволяет сделать это эффективно.

Пример

import numpy as np

df_cond = pd.DataFrame({
    'value': [1, np.nan, 3, np.nan, 5],
    'cat': ['good', 'bad', 'good', 'bad', 'good']
})
# Если категория 'good' - заполняем 0, иначе 100
df_cond['value_filled'] = np.where(
    df_cond['value'].isna(),
    np.where(df_cond['cat'] == 'good', 0, 100),
    df_cond['value']
)
print(df_cond)
  
   value   cat  value_filled
0    1.0  good           1.0
1    NaN   bad         100.0
2    3.0  good           3.0
3    NaN   bad         100.0
4    5.0  good           5.0
  

Этот подход легко масштабируется на несколько условий с помощью вложенных np.where или np.select.

Ошибка:

При использовании np.where результат - массив numpy, а не Series, поэтому теряются индексы. В примере мы создали новый столбец DataFrame, что сохраняет индексы.

Обработка пропущенных значений (None/NaN) в Python - comments

En
Python пропущенные значения (python)