2025, Oct 19 00:16
Как в Pandas автоматически приводить столбцы Decimal к float64 без хардкода
Как в Pandas DataFrame автоматически выявлять столбцы с decimal.Decimal и приводить их к float64 без хардкода: пороги обнаружения и практические советы.
При работе с финансовыми или измерительными данными нередко приходится иметь дело с decimal.Decimal. Как только такие значения попадают в Pandas DataFrame, соответствующие столбцы обычно получают тип object — не лучший вариант, если вы рассчитываете на «числовое» поведение из коробки. Задача — автоматически приводить все столбцы на базе Decimal к float64 уже на этапе создания, не прописывая названия столбцов вручную.
Подготовка
Рассмотрим словарь, в котором есть список значений decimal.Decimal. После создания DataFrame соответствующий столбец получает тип object, а не float64:
import pandas as pd
from decimal import Decimal
payload = {
    'Item': ['Apple', 'Banana', 'Orange'],
    'Price': [Decimal('1.25'), Decimal('0.75'), Decimal('2.00')],
    'Quantity': [10, 20, 15]
}
frame = pd.DataFrame(payload)
print(frame.dtypes)
# Вывод:
# Item        object
# Price       object
# Quantity     int64
# dtype: object
Даже pd.DataFrame.from_records(..., coerce_float=True) не изменит лежащие в основе значения Decimal. И хотя .astype(float) справляется, когда столбец известен, это не спасает, если заранее неизвестны имена столбцов.
Почему Decimal превращается в object
Когда столбец содержит экземпляры Decimal, Pandas выводит обобщённый тип object. Так сохраняются исходные объекты Python, но теряется «родное» числовое поведение. Решение — определить, какие столбцы действительно содержат значения Decimal, и за один проход конвертировать их в float64.
Рабочий шаблон преобразования
Практичный подход — выводить нужные столбцы из самих данных, собрать отображение и передать его в .astype. Если первой строке можно доверять как репрезентативной, используйте её для обнаружения столбцов с Decimal и их приведения:
from decimal import Decimal
probe = frame.iloc[0].map(type).eq(Decimal)
cast_map = dict.fromkeys(frame.columns[probe], float)
# Пример: {'Price': float}
converted = frame.astype(cast_map)
Так логика остаётся основанной на данных и избегает жёсткого перечисления имён столбцов.
Выбор порога выявления
Если опираться только на первую строку слишком рискованно, задайте порог по всему столбцу. В зависимости от требований можно конвертировать столбец, когда все значения — Decimal, когда встречается хотя бы одно значение Decimal, или когда доля значений Decimal превышает 90%:
# Конвертировать столбец, если все значения — Decimal
cast_map = dict.fromkeys(frame.columns[frame.map(type).eq(Decimal).all()], float)
# Конвертировать, если есть хотя бы одно значение Decimal
cast_map = dict.fromkeys(frame.columns[frame.map(type).eq(Decimal).any()], float)
# Конвертировать, если более 90% значений — Decimal
cast_map = dict.fromkeys(
    frame.columns[frame.map(type).eq(Decimal).mean().gt(0.9)],
    float
)
converted = frame.astype(cast_map)
После преобразования соответствующие столбцы ведут себя как настоящие числовые. Пример результирующих dtypes выглядит так:
Item        string[python]
Price              float64
Quantity             Int64
dtype: object
Почему это важно
Аккуратная работа с типами в DataFrame критична для предсказуемых численных операций, агрегирований и совместимости с другими библиотеками. Если оставлять насыщенные Decimal столбцы в виде object, легко нарваться на скрытые проблемы — от просадок по производительности до неожиданного поведения в последующих вычислениях. Декларативное приведение на этапе создания обеспечивает единообразные числовые типы без поддержки жёсткого списка имён столбцов.
Итоги
Если во входных данных встречается decimal.Decimal, доверьтесь самим данным: определите, какие столбцы нужно приводить. Найдите столбцы со значениями Decimal, постройте отображение к float и примените .astype. Будь то доверие к первой строке или порог по всему столбцу — принцип один: без хардкода, декларативно и с гарантией, что числовые столбцы ведут себя как числовые.