2025, Dec 16 03:01

TypeError float object is not iterable в MultiLabelBinarizer: причина — индекс в pandas и как исправить

Почему при one-hot кодировании в pandas с MultiLabelBinarizer возникает TypeError: float object is not iterable. Разбор выравнивания индекса и рабочий фикс.

One‑hot кодирование многозначных полей в опросе Stack Overflow 2024 кажется простым, пока внезапно не ломается на, казалось бы, таком же столбце. Типичный сценарий: кодирование Employment проходит, а при кодировании LanguageAdmired появляется TypeError: float object is not iterable. Подвох не в самом MultiLabelBinarizer, а в том, как pandas выравнивает строки при конкатенации закодированных фреймов после фильтрации и удаления дубликатов.

Воспроизведение проблемы

В наборе данных значения в столбцах Employment и LanguageAdmired разделены точкой с запятой. Задача — разбить их на списки и применить MultiLabelBinarizer. Ниже фрагмент кода: он кодирует Employment, конкатенирует результат, а затем пытается закодировать LanguageAdmired — на этом шаге и возникает ошибка.

import pandas as pd
from sklearn.preprocessing import MultiLabelBinarizer
src = 'survey_results_public.csv'
data = pd.read_csv(src)
data.drop('ResponseId', axis=1, inplace=True)
data = data[~data.duplicated(keep='first')].copy()
data['LanguageAdmired'] = data['LanguageAdmired'].fillna('Other')
data['LanguageAdmired'] = data['LanguageAdmired'].str.split(';')
data['Employment'] = data['Employment'].str.split(';')
enc_emp = MultiLabelBinarizer()
emp_mat = enc_emp.fit_transform(data['Employment'])
emp_ohe = pd.DataFrame(emp_mat, columns=['Employment_' + v for v in enc_emp.classes_])
data = pd.concat([data, emp_ohe], axis=1).copy()
enc_lang = MultiLabelBinarizer()
lang_mat = enc_lang.fit_transform(data['LanguageAdmired'])  # TypeError appears here

Почему это происходит

Проверка data.shape до и после конкатенации показывает, что появляются новые строки. Последовательность drop, за которой следует удаление дубликатов, меняет исходный индекс. Когда первый закодированный фрейм создаётся без передачи исходного индекса, он получает новый RangeIndex. Конкатенация с отфильтрованными данными заставляет pandas выравнивать по индексу, а не по порядку строк. Любые несовпадающие индексы порождают новые строки с NaN в остальных столбцах, включая LanguageAdmired. Следующий вызов MultiLabelBinarizer получает NaN вместо списка, и, поскольку в pandas NaN — это float, трансформер выбрасывает TypeError: float object is not iterable.

Есть и второе наблюдение в ту же сторону: если вовсе убрать первый шаг конкатенации, конвейер отрабатывает, потому что в середине пайплайна нет «косого» объединения, которое занесло бы NaN в LanguageAdmired.

Как исправить

Решение — сохранять исходный индекс при построении закодированных DataFrame. Тогда concat корректно выравнивает строки, не создаёт лишних записей и не вносит NaN в следующие входы.

import pandas as pd
from sklearn.preprocessing import MultiLabelBinarizer
src = 'survey_results_public.csv'
data = pd.read_csv(src)
data.drop('ResponseId', axis=1, inplace=True)
data = data[~data.duplicated(keep='first')].copy()
data['LanguageAdmired'] = data['LanguageAdmired'].fillna('Other')
data['LanguageAdmired'] = data['LanguageAdmired'].str.split(';')
data['Employment'] = data['Employment'].str.split(';')
enc_emp = MultiLabelBinarizer()
emp_mat = enc_emp.fit_transform(data['Employment'])
emp_ohe = pd.DataFrame(
    emp_mat,
    columns=['Employment_' + v for v in enc_emp.classes_],
    index=data.index,
)
enc_lang = MultiLabelBinarizer()
lang_mat = enc_lang.fit_transform(data['LanguageAdmired'])
lang_ohe = pd.DataFrame(
    lang_mat,
    columns=['LanguageAdmired_' + v for v in enc_lang.classes_],
    index=data.index,
)
data = pd.concat([data, emp_ohe, lang_ohe], axis=1)

Когда индекс сохраняется у обоих закодированных фреймов, конкатенация не добавляет лишних строк, а LanguageAdmired остаётся столбцом со списками для каждой существующей записи, поэтому MultiLabelBinarizer работает без ошибок.

Почему это важно

По замыслу pandas выравнивает данные по индексу. Всякий раз, когда строки удаляются, фильтруются или дедуплицируются, индекс может стать разреженным или сместиться относительно заново созданных фреймов. Если после этого склеить фреймы с новым RangeIndex, pandas покорно создаст дополнительные строки — часто это позже проявляется как неожиданные NaN и проблемы с типами. В этом пайплайне одна неверно выровненная конкатенация превратила списки на входе в float для части строк, что и всплыло в трансформере как TypeError.

Полезно также проверять конвейер быстрыми сверками форм (shape) до и после concat и использовать «print‑отладку», чтобы посмотреть, что именно находится в столбце перед передачей в трансформер. Простые выводы типа, длины и первых значений быстро показывают, где появляется NaN или неожиданные типы.

Основные выводы

При one‑hot кодировании многозначных столбцов с помощью MultiLabelBinarizer держите под контролем выравнивание строк. Если вы фильтровали или дедуплицировали DataFrame, сохраняйте исходный индекс при преобразовании закодированных массивов в DataFrame. Конкатенируйте после того, как оба кодирования готовы, или убеждайтесь, что каждый промежуточный DataFrame несёт index=data.index. Если возникла похожая ошибка, сперва сравните размеры на разных шагах и проверьте, не занёс ли «несовпадающий» concat NaN — обычно достаточно исправить индексы, и проблема уходит.