Урок курса
Пропуски в таблице: обнаружение, dropna, fillna и nullable dtype
NumPy и pandas: практический тренажёрВ числовых массивах NumPy пропуск в рассмотренном float-сценарии представлен как np.nan, и там его проверяют np.isnan(). Pandas работает с таблицами, где в одном DataFrame могут соседствовать числа, строки и булевы значения — и каждый тип хранит «отсутствие данных» по-своему: np.nan, None или pd.NA. Поэтому pandas предлагает единый интерфейс — isna/notna — который скрывает эти различия и даёт одинаковую маску вне зависимости от dtype столбца.
Обнаружение пропусков: isna, notna и подсчёт через sum
Первый шаг при работе с любыми реальными данными — понять, где именно чего-то не хватает. df.isna() возвращает DataFrame той же формы, но заполненный булевыми значениями: True там, где значение пропущено, False — где данные есть. df.notna() — прямая инверсия: True означает наличие данных.
Оба метода работают одинаково для DataFrame и для Series — форма результата всегда совпадает с формой исходного объекта.
Возьмём таблицу с явными пропусками в разных столбцах:
import pandas as pd
df = pd.DataFrame({
'age': pd.array([25, None, 32], dtype='Int64'),
'salary': [50000.0, float('nan'), 75000.0],
'dept': ['IT', None, 'HR']
})
print(df.isna())
Результат:
age salary dept 0 False False False 1 True True True 2 False False False
Здесь вся вторая строка пропущена. Но в реальных данных пропуски рассеяны, и смотреть на полную матрицу неудобно. Чтобы получить сводку по столбцам, достаточно применить .sum() — булевые значения трактуются как 0/1, и сумма даёт количество пропусков в каждом столбце:
print(df.isna().sum())
age 1 salary 1 dept 1 dtype: int64
Если хочется знать долю пропусков, а не абсолютное число, достаточно разделить на длину таблицы:
print(df.isna().sum() / len(df))
Это стандартный способ быстро оценить «засорённость» данных до того, как принимать решение — удалять строки или заполнять.
Ещё один полезный приём — df.isna().any(): возвращает Series с True по тем столбцам, где есть хоть один пропуск. Удобно для быстрой проверки, а не полного подсчёта:
print(df.isna().any())
age True salary True dept True dtype: bool
На уровне отдельного столбца series.isna() возвращает обычную булеву Series, которую можно сразу использовать для фильтрации. Например, чтобы посмотреть только строки, где salary пропущен:
print(df[df['salary'].isna()])
В результате получаем только строки с пропуском в salary — это удобно для ручной проверки или для отладки логики заполнения.
Диагностика через isna().sum() всегда должна быть первым шагом: она показывает масштаб проблемы и помогает решить, какой инструмент применять дальше — удалять записи или заполнять пропуски.
Удаление строк и столбцов с пропусками через dropna
Когда диагностика через isna().sum() показывает, что пропусков немного и строки с ними можно просто выбросить без потери смысла, на сцену выходит dropna.
По умолчанию df.dropna() удаляет любую строку, в которой есть хотя бы один пропуск. Для нашей таблицы это означает, что строка с индексом 1 — где пропущены age, salary и dept — исчезнет из результата:
import pandas as pd
df = pd.DataFrame({
'age': pd.array([25, None, 32], dtype='Int64'),
'salary': [50000.0, float('nan'), 75000.0],
'dept': ['IT', None, 'HR']
})
clean = df.dropna()
print(clean)
age salary dept 0 25 50000.0 IT 2 32 75000.0 HR
dropna возвращает новый DataFrame — исходный df при этом не меняется, и строка с индексом 1 в нём по-прежнему присутствует. Необратимой потерей данных это становится только если вы перезаписываете исходную переменную (df = df.dropna()) или теряете ссылку на оригинальный объект. Пока df жив, данные можно достать.
Параметр how
Если нужно удалять строку только тогда, когда в ней пустые абсолютно все поля, используется how='all':
df2 = pd.DataFrame({
'age': pd.array([25, None, None], dtype='Int64'),
'salary': [50000.0, float('nan'), None],
'dept': ['IT', 'Finance', None]
})
print(df2.dropna(how='all'))
age salary dept
0 25 50000.0 IT
1 <NA> NaN Finance
Строка с индексом 1 содержит частичные данные (dept есть), поэтому how='all' её сохраняет. Строка 2 полностью пустая — она исчезает.
Параметр subset
Часто имеет смысл смотреть на пропуски только в ключевых столбцах. Если пропуск в dept допустим, а пропуск в salary — нет, указываем subset:
print(df.dropna(subset=['salary']))
age salary dept 0 25 50000.0 IT 2 32 75000.0 HR
Здесь строки отфильтрованы только по столбцу salary. Если бы пропуск был только в dept, строка бы осталась.
Параметр axis=1
Если пропуски сосредоточены в целом столбце — например, признак вообще не заполнялся — удобнее выбросить столбец, а не строки:
df3 = pd.DataFrame({
'age': [25, 30, 32],
'salary': [50000.0, float('nan'), 75000.0],
'score': [float('nan'), float('nan'), float('nan')]
})
print(df3.dropna(axis=1, how='all'))
age salary 0 25 50000.0 1 30 NaN 2 32 75000.0
Столбец score полностью пустой — он удаляется. salary с одним пропуском остаётся, потому что how='all' требует, чтобы все значения в столбце были пропущены.
dropna — это инструмент для ситуаций, когда строка или столбец с пропуском не несёт полезной информации. Если данные можно восстановить или заменить осмысленным значением, следующий шаг — fillna и распространение значений.
Заполнение пропусков: константа через fillna и распространение через ffill/bfill
Там, где удалять строки нельзя — данных и без того мало, или строка ценна частично, — пропуск нужно заполнить. Два главных инструмента для этого: fillna для подстановки фиксированного значения и ffill/bfill для распространения соседних значений.
fillna(value) — константное заполнение
series.fillna(value) заменяет каждый пропуск в Series скалярным значением value. df.fillna(value) делает то же самое для всей таблицы сразу. Исходный объект при этом не меняется — метод возвращает новый объект:
import pandas as pd
df = pd.DataFrame({
'age': pd.array([25, None, 32], dtype='Int64'),
'salary': [50000.0, float('nan'), 75000.0],
'dept': ['IT', None, 'HR']
})
# Заполняем только salary
filled_salary = df['salary'].fillna(0.0)
print(filled_salary)
0 50000.0 1 0.0 2 75000.0 Name: salary, dtype: float64
Для разных столбцов можно передать словарь — тогда каждый столбец получит своё значение:
df_filled = df.fillna({'salary': 0.0, 'dept': 'Unknown'})
print(df_filled)
age salary dept
0 25 50000.0 IT
1 <NA> 0.0 Unknown
2 32 75000.0 HR
Заметьте: age всё ещё <NA>, потому что в словаре мы его не указали.
Подбирать значение для заполнения нужно осмысленно: 0 в salary говорит «зарплата равна нулю», а это может исказить агрегаты. Иногда правильнее заполнить медианой или средним — но это уже задача анализа, а не синтаксиса fillna.
ffill и bfill — распространение соседних значений
Когда данные упорядочены по времени или по какой-то логике и пропуск — это просто «пропуск в записи, а не в реальности», удобнее взять ближайшее известное значение.
ffill() (forward fill) распространяет последнее известное значение вниз — каждый пропуск заменяется тем, что стоит выше него:
s = pd.Series([10.0, float('nan'), float('nan'), 40.0, float('nan')])
print(s.ffill())
0 10.0 1 10.0 2 10.0 3 40.0 4 40.0 dtype: float64
bfill() работает в обратном направлении — заполняет пропуск ближайшим значением снизу:
print(s.bfill())
0 10.0 1 40.0 2 40.0 3 40.0 4 NaN dtype: float64
Оба метода имеют граничный случай, который легко упустить: если пропуск стоит в самом начале Series, ffill не может его заполнить — выше нет данных. Аналогично bfill не заполнит пропуск в самом конце — ниже ничего нет. В обоих случаях пропуск просто остаётся NaN.
В примере выше последний элемент после bfill так и остался NaN — именно по этой причине.
Оба метода применимы и к DataFrame — тогда заполнение происходит вдоль каждого столбца независимо:
print(df['salary'].ffill())
0 50000.0 1 50000.0 2 75000.0 Name: salary, dtype: float64
Здесь пропуск в строке 1 заполнился значением из строки 0 — 50000.0.
Выбор между fillna, ffill и bfill диктует природа данных: для временного ряда ffill часто уместен, для категориальной переменной — скорее константа или мода. Что именно происходит с dtype после заполнения — тема следующей секции.
Nullable dtype: Int64, Float64, boolean, StringDtype и поведение при fillna
Когда пропуски заполнены, возникает вопрос, который легко проигнорировать: а какой тип данных теперь у столбца? Ответ зависит от того, каким был dtype до заполнения — и здесь кроется принципиальная разница между NumPy-backed типами и nullable-типами pandas.
Проблема с обычным int64
В NumPy целочисленный массив (int64) не умеет хранить пропуск — у целых чисел просто нет специального sentinel-значения вроде float('nan'). Поэтому когда в столбце с целыми числами появляется пропуск, pandas вынужден переводить его в float64, где пропуск представлен NaN. Это происходит во многих, но не во всех сценариях — конкретное поведение зависит от операции и версии pandas.
Результат: данные, которые были целыми числами, начинают выглядеть как 25.0, 32.0, и агрегаты могут вести себя иначе, чем ожидалось.
Nullable-типы как решение
Pandas предоставляет альтернативу — nullable-типы, которые поддерживают pd.NA рядом с обычными значениями без потери типа:
Int64(заглавнаяI) — целые числа с пропускамиFloat64(заглавнаяF) — вещественные числа с пропускамиboolean— булевы значения с пропускамиpd.StringDtype()— строки с пропусками
Задаются через astype:
import pandas as pd
df = pd.DataFrame({
'age': pd.array([25, None, 32], dtype='Int64'),
'salary': [50000.0, float('nan'), 75000.0],
'dept': ['IT', None, 'HR']
})
print(df['age'].dtype) # Int64
print(df['salary'].dtype) # float64
# Переводим salary в nullable Float64
df['salary'] = df['salary'].astype('Float64')
print(df['salary'].dtype) # Float64
# Переводим dept в StringDtype
df['dept'] = df['dept'].astype(pd.StringDtype())
print(df['dept'].dtype) # string
Столбец age уже создан с dtype='Int64' явно — None в нём хранится как pd.NA, а не превращается в NaN с потерей целочисленного типа.
Что происходит с dtype после fillna
Если заполнять nullable-столбец значением совместимого типа, dtype сохраняется:
filled_age = df['age'].fillna(0)
print(filled_age.dtype) # Int64 — тип не изменился
filled_salary = df['salary'].fillna(0.0)
print(filled_salary.dtype) # Float64 — тип не изменился
Для сравнения — обычный float64 после fillna остаётся float64, потому что NaN в нём изначально тот же float. Но если попытаться создать столбец как обычный int64 и добавить в него пропуск через операцию, поведение может оказаться неожиданным: pandas нередко повышает тип до float64.
Как проверить dtype после операций
Привычка проверять dtype после заполнения — полезная. Особенно если столбец участвует в дальнейших вычислениях:
print(df.dtypes)
age Int64 salary Float64 dept string dtype: object
Если dtype изменился неожиданно (например, Int64 стал object), значит где-то в пайплайне значение оказалось несовместимого типа.
Сквозной пример: от сырых данных до заполненного nullable-DataFrame
import pandas as pd
df = pd.DataFrame({
'a': pd.array([1, None, 3], dtype='Int64'),
'b': [10.0, float('nan'), 30.0],
'c': ['x', None, 'z']
})
# Переводим все столбцы в nullable-типы
df['b'] = df['b'].astype('Float64')
df['c'] = df['c'].astype(pd.StringDtype())
# Проверяем пропуски
print(df.isna().sum())
# Заполняем
df_filled = df.fillna({'a': 0, 'b': 0.0, 'c': 'Unknown'})
print(df_filled)
print(df_filled.dtypes)
a 1 b 1 c 1 dtype: int64 a b c 0 1 10.0 x 1 0 0.0 Unknown 2 3 30.0 z a Int64 b Float64 c string dtype: object
Все три столбца сохранили свои nullable-типы после fillna — именно это и отличает Int64/Float64/string от NumPy-backed аналогов. Ключевое правило: nullable dtype сохраняется после fillna, если значение-заменитель совместимо с типом столбца; если передать несовместимый тип, pandas может повысить dtype до object.
Закрепите тему на задачах
Посчитайте пропуски до и после обработки. Не заменяйте отсутствие измерения нулём без объяснения смысла, а для ffill проверьте порядок строк и границы групп.
pandas dropna(subset): какие строки удалятся
Задача на pandas fillna и nullable dtype: обработка пропусков
pandas dropna: how, subset и форма таблицы
Откройте условие и решите задачу перед чтением следующей темы. В задачах на код проверяется программа, в тестовых заданиях — выбранный ответ. Для последовательного обучения вернитесь в программу бесплатного курса.
Задача 1
DataFrame df имеет столбцы 'a', 'b', 'c'. Строка с индексом 2 содержит пропуски в столбцах 'a' и 'c'; строка с индексом 5 содержит пропуск только в 'b'. Вызывается df.dropna(subset=['a', 'c']). Какие строки будут удалены?
Задача 2
Series создан как:
import pandas as pd
s = pd.Series(pd.array([None, 10, None, 30, None], dtype='Int64'))
Что вернёт s.ffill().tolist()?
Задача 3
Столбец df['count'] имеет dtype float64 и содержит пропуски (NaN). Разработчик хочет хранить целые числа вместе с пропусками. Какой nullable dtype нужно указать в astype(), чтобы столбец мог хранить целые числа вместе с pd.NA?
Задача 4
Пропуски в журнале датчиков: isna, dropna, fillna и ffill за один проход
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая обрабатывает журнал показаний датчика:
- Читает из
stdinцелое числоn— количество строк журнала. - Читает следующие
nстрок; каждая содержит два токена через пробел:hour(целое число от 0 до 23) иvalue(вещественное число или строкаnan). Создаётpd.DataFrameс именемdfсо столбцами'hour'(dtypeint64) и'value'(dtypefloat64). - Шаг A. Выводит одну строку:
missing=<k>, гдеk— число пропущенных значений в столбце'value'(черезdf['value'].isna().sum()). - Шаг B. Удаляет строки, где
'value'пропущен, черезdf.dropna(subset=['value']). Сохраняет результат вdf_clean. Выводит одну строку:clean_shape=<shape>, напримерclean_shape=(4, 2). - Шаг C. Заполняет пропуски в
df['value']константой0.0черезfillna. Сохраняет результат вs_filled. Выводит значенияs_filledчерез пробел, округлив до одного знака. - Шаг D. Заполняет пропуски в
df['value']методомffill(). Сохраняет результат вs_ffill. Выводит значенияs_ffillчерез пробел, округлив до одного знака. Если первый элемент оказался пропуском иffillне смог его заполнить — выводитnanдля этой позиции.
Во всех шагах исходный df не изменяется.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n hour0 value0 hour1 value1 ...
Токен nan в поле value означает пропуск.
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
Ровно четыре строки в порядке A → B → C → D.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
Nullable dtype и fillna: Int64, Float64, boolean после заполнения пропусков
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит три токена через пробел:
qty— целое число или строкаNA;price— вещественное число или строкаNA;active— строкаTrue,FalseилиNA.
- Строит
pd.DataFrameс именемdfсо столбцами:
'qty'с dtypeInt64(nullable integer);'price'с dtypeFloat64(nullable float);'active'с dtypeboolean(nullable boolean).
- Шаг A. Выводит одну строку:
isna_sum=<qty_na> <price_na> <active_na>— число пропусков по каждому столбцу через пробел. - Шаг B. Заполняет пропуски в
df['qty']значением0черезfillna(0), сохраняет вqty_filled. Выводит одну строку:qty_dtype=<dtype>, где<dtype>—dtypeрезультата. - Шаг C. Заполняет пропуски в
df['price']черезffill(), сохраняет вprice_ffilled. Выводит значения через пробел, округлив до одного знака. Если первый элемент — пропуск иffillне смог его заполнить, выводитNAдля этой позиции. - Шаг D. Заполняет пропуски в
df['active']значениемFalseчерезfillna(False), сохраняет вactive_filled. Выводит одну строку:active_dtype=<dtype>, где<dtype>—dtypeрезультата.
Во всех шагах исходный df не изменяется.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n qty0 price0 active0 qty1 price1 active1 ...
Токен NA означает пропуск в соответствующем столбце.
Формат вывода
Ровно четыре строки в порядке A → B → C → D.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 6
dropna с how и subset: диагностика формы после каждого удаления
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит четыре токена через пробел:
id— целое число;region— строка илиNA(пропуск);amount— вещественное число илиNA(пропуск);tag— строка илиNA(пропуск).
- Строит
pd.DataFrameс именемdfсо столбцами'id'(int64),'region'(object),'amount'(float64),'tag'(object). ТокенNAзаменяется наNoneдля строковых столбцов и наfloat('nan')для числового. - Шаг A. Вычисляет
df.isna().sum()и выводит строку:
isna_sum=<id> <region> <amount> <tag>— четыре числа через пробел. - Шаг B. Применяет
df.dropna(how='any'). Выводитdrop_any=<shape>, напримерdrop_any=(3, 4). - Шаг C. Применяет
df.dropna(subset=['region', 'amount'], how='all'). Выводитdrop_subset_all=<shape>. - Шаг D. Применяет
df.dropna(axis=1, how='all'). Выводитdrop_cols=и следом через пробел имена оставшихся столбцов. Если удалённых столбцов нет — выводит толькоdrop_cols=и пустую строку (то естьdrop_cols=\n).
Во всех шагах исходный df не изменяется.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n id0 region0 amount0 tag0 id1 region1 amount1 tag1 ...
Токен NA означает пропуск в соответствующем столбце.
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
Ровно четыре строки в порядке A → B → C → D.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 7
Найди и исправь: три ошибки в bfill, dropna(subset) и dtype после fillna
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет.
В редакторе уже есть сломанный код. В нём ровно три ошибки в трёх независимых строках:
- Для заполнения пропусков в столбце
'temp'вперёд (каждый пропуск заменяется последним известным значением выше) используетсяbfill()— хотя нуженffill(). dropnaвызывается сsubset=['station'], хотя по условию нужно удалять строки, где пропущен именно столбец'temp'— то естьsubset=['temp'].- Для заполнения пропусков в столбце
'qty'(dtypeInt64) используетсяfillna(-1), но результат приводится через.astype('float64')— хотя nullable-типInt64сохраняется послеfillnaцелым числом и приводить к float не нужно; выводить нужноdtypeрезультатаfillnaбезastype.
Каждая ошибка находится в отдельной строке кода. Исправьте все три.
Что нужно сделать
Исправьте три ошибки так, чтобы программа:
- Читала из
stdinцелое числоn— количество строк таблицы. - Читала следующие
nстрок; каждая содержит три токена через пробел:station(строка без пробелов),temp(вещественное число илиnan),qty(целое число илиNA). Создавалаpd.DataFrameсо столбцами'station'(object),'temp'(float64),'qty'(Int64). - Шаг A. Заполняла пропуски в
df['temp']методом forward fill и сохраняла вs_ff. Выводила значенияs_ffчерез пробел, округлив до одного знака. Если первый элемент — пропуск иffillне смог его заполнить — выводилаnan. - Шаг B. Применяла
df.dropna(subset=['temp'])и выводила shape результата в видеdrop_shape=<shape>. - Шаг C. Заполняла пропуски в
df['qty']значением-1черезfillna(-1)и выводилаqty_dtype=<dtype>— dtype результата (без astype).
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n station0 temp0 qty0 station1 temp1 qty1 ...
Токен nan в поле temp означает пропуск (float NaN). Токен NA в поле qty означает пропуск (pd.NA).
Дополнительные ограничения: n >= 1; таблица содержит явно описанные в условии столбцы.
Формат вывода
Ровно три строки в порядке A → B → C.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- pandas dropna subset: какие строки удаляются и почему
Как dropna(subset=...) учитывает пропуски только в выбранных столбцах? Минимальный пример pandas и вопрос с проверкой ответа.
- pandas fillna: заполнение пропусков и nullable-типы Int64
Как заполнить пропуски и сохранить Int64, Float64 и boolean в pandas? Пример fillna и задача на nullable dtype с проверкой кода.
- pandas dropna: how, subset и форма таблицы
Удалите строки с пропусками разными правилами dropna(how=...) и subset, сравните форму DataFrame после каждого шага.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
